"""
#===============================================================================
# License
#===============================================================================
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

#===============================================================================
Copyright (c) 2009 bryan p. conrad
bconrad2@gmail.com

Repository Path:    
$HeadURL$

Created on:           Sep 5, 2009
Last Revised on:      $Date$
Version:              $Revision$
ID:                   $Id$
Author:               $Author$ :: (bryan)

#===============================================================================


#===============================================================================
# Documentation
#===============================================================================

"""

#===============================================================================
# Imports
#===============================================================================

import sys
import os
import time
import vtk
from PyQt4 import QtCore, QtGui
from QVTKRenderWindowInteractor import *
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from matplotlib import cm
from matplotlib import pyplot


import numpy as np
#import JT.qtImageCanvas 
#import JT.qtDRR
import JT
import JT.Data2

# create alias for QT file open dialog
getOpenFileName = QtGui.QFileDialog.getOpenFileName
getOpenFileNames = QtGui.QFileDialog.getOpenFileNames

# QT codes for directions keys
LEFT = 16777234
RIGHT = 16777236
UP = 16777235
DOWN = 16777237

DEBUG = False

class MainWindow(QtGui.QMainWindow):
    def __init__(self, parent=None):
        # Initialize some variables
        #################################
        # Save volumes so that they do not have to be recreated each time
        self.cached_volumes = {}
        self.cached_surfs = {}
        self.cached_images = {}
        self._roi = None
        # Support for multiple volumes in DRR at the same time (will not optimze)
        self.multi_volume = False
        #self.activeImage = 0
        #self.activeVolume = 0
        self.activeCal = 0
        self.cur_dir = ''
        self.drr = None
        self.surf = None
        self.reg = None
        self.tabs = True
        self.interactiveReg = None
        # Create Animate Window to display 3D results
        self.external_viewer_list = []
        self.external_viewer = JT.Animate()
        self.projectFileName = ''
        self.fixedImage_paths = []
        self.volume_paths = []
        self.cal_paths = []
        # For DRR interaction
        self._keyboardStepSize = 1
        self._rotStepSize = 1
        
        # For Data structure
        self.Data = JT.Data2.DataStructure()
        self.frame_nodes = []
        self.images_nodes = []
        self.volume_nodes = []
        self.pose_nodes = []
        self.cal_nodes = []
        self.chart_lines1 = []
        self.chart_lines2 = []
        #################################
        
        QtGui.QMainWindow.__init__(self, parent)
        QtGui.QApplication.setStyle('plastique')
        
        # Create Data Structure
        self.Data = JT.Data2.DataStructure()
        
        mainFrame = QtGui.QFrame(self)
        self.setCentralWidget(mainFrame)
        
        
#===============================================================================
#        Need to Subclass the (rambling) code below so that it is easier to maintain
#===============================================================================
        # Setup Left Panel (image list and volume list)
        #Image list
        self.progressBar = QtGui.QProgressBar()
        self.progressBar.setMinimum(0)
        self.imageListModel = MyFixedImageListModel(self.Data, self)
        self.imageListView = QtGui.QListView()
        self.imageListView.setModel(self.imageListModel)
        self.imageListView.setResizeMode(QtGui.QListView.Adjust)
        self.connect(self.imageListView,
                     QtCore.SIGNAL("clicked(const QModelIndex&)"),
                     self.selectImage)
        imageLabel = QtGui.QLabel("Image List")
        imageLabel.setBuddy(self.imageListView)
        imageListAndLabel = QtGui.QFrame()
        imageListAndLabelLayout = QtGui.QVBoxLayout()
        imageListAndLabel.setLayout(imageListAndLabelLayout)
        imageListAndLabelLayout.addWidget(imageLabel)
        imageListAndLabelLayout.addWidget(self.imageListView)
        #Volume List
        self.volumeListModel = MyListModel([], self)
        self.volumeListView = QtGui.QListView()
        self.volumeListView.setModel(self.volumeListModel)
        self.volumeListView.setResizeMode(QtGui.QListView.Adjust)
        self.connect(self.volumeListView,
                     QtCore.SIGNAL("clicked(const QModelIndex&)"),
                     self.selectVolume)
        volumeLabel = QtGui.QLabel("Volume List")
        volumeLabel.setBuddy(self.volumeListView)
        volumeListAndLabel = QtGui.QFrame()
        volumeListAndLabelLayout = QtGui.QVBoxLayout()
        volumeListAndLabel.setLayout(volumeListAndLabelLayout)
        volumeListAndLabelLayout.addWidget(volumeLabel)
        volumeListAndLabelLayout.addWidget(self.volumeListView)
        #Cal list
        self.calListModel = MyListModel([], self)
        calListView = QtGui.QListView()
        calListView.setModel(self.calListModel)
        calListView.setResizeMode(QtGui.QListView.Adjust)
        #calListView.setGridSize(QtCore.QSize(6,1))
        self.connect(calListView,
                     QtCore.SIGNAL("clicked(const QModelIndex&)"),
                     self.selectCal)
        calLabel = QtGui.QLabel("Cal List")
        calLabel.setBuddy(calListView)
        calListAndLabel = QtGui.QFrame()
        calListAndLabelLayout = QtGui.QVBoxLayout()
        calListAndLabel.setLayout(calListAndLabelLayout)
        calListAndLabelLayout.addWidget(calLabel)
        calListAndLabelLayout.addWidget(calListView)
        
        leftPanel = QtGui.QFrame()
        leftPanelLayout = QtGui.QGridLayout()
        leftPanel.setLayout(leftPanelLayout)
        leftPanelLayout.addWidget(self.progressBar,0,0,1,1)
        leftPanelLayout.addWidget(imageLabel,1,0,1,1)
        leftPanelLayout.addWidget(self.imageListView,2,0,1,1)
        leftPanelLayout.setRowMinimumHeight(2,400)
        leftPanelLayout.setColumnMinimumWidth(0,200)
        leftPanelLayout.addWidget(volumeLabel,3,0,1,1)
        leftPanelLayout.addWidget(self.volumeListView,4,0,1,1)
        leftPanelLayout.addWidget(calLabel,5,0,1,1)
        leftPanelLayout.addWidget(calListView,6,0,1,1)
        leftPanelLayout.setRowMinimumHeight(6,50)
        leftPanelSplitter = QtGui.QSplitter()
        leftPanelSplitter.setOrientation(QtCore.Qt.Vertical)
        leftPanelSplitter.addWidget(imageListAndLabel)
        leftPanelSplitter.addWidget(volumeListAndLabel)
        leftPanelSplitter.addWidget(calListAndLabel)
        
        # Setup center panel (Fixed Image and DRR render windows)
        self.tabWidget = QtGui.QTabWidget()
        renderSplitter = QtGui.QSplitter()
        self.tabWidget.setFixedSize(512,512)
        fixedImageFrame = QtGui.QFrame()
        self.fixedImage = JT.QTImageCanvas(fixedImageFrame)
        self.drrFrame = QtGui.QFrame()
        checkerImageFrame = QtGui.QFrame()
        self.checkerImage = JT.QTImageCanvas(checkerImageFrame)
        colorImageFrame = QtGui.QFrame()
        self.colorImage = JT.QTImageCanvas(colorImageFrame)
        shadeImageFrame = QtGui.QFrame()
        self.shadeImage = JT.QTWipeImageCanvas(shadeImageFrame)
        
        self.connect(self.fixedImage.iren,
                     QtCore.SIGNAL("roi_changed(PyQt_PyObject)"),
                     self.updateROI)
        self.connect(self.fixedImage.iren,
                     QtCore.SIGNAL("color_window_changed(PyQt_PyObject)"),
                     self.updateColorWindow)
        self.connect(self.fixedImage.iren,
                     QtCore.SIGNAL("window_level_changed(PyQt_PyObject)"),
                     self.updateWindowLevel)
        
        self.shadeImage.iren.AddObserver("KeyPressEvent",self.shadeKeyPress)
        self.checkerImage.iren.AddObserver("KeyPressEvent",self.checkerKeyPress)

        # uses tabs for render views
        self.tabWidget.addTab(fixedImageFrame,"&Fixed Image")
        self.tabWidget.addTab(self.drrFrame,"&DRR")
        self.tabWidget.addTab(checkerImageFrame,"&Checker Image")
        self.tabWidget.addTab(colorImageFrame, "Colo&r Image")
        self.tabWidget.addTab(shadeImageFrame, "&Windowshade")
        # Initially start DRR related tabs disabled
        self.tabWidget.setTabEnabled(1,False)
        self.tabWidget.setTabEnabled(2,False)
        self.tabWidget.setTabEnabled(3,False)
        self.tabWidget.setTabEnabled(4,False)
        self.connect(self.tabWidget, 
                 QtCore.SIGNAL("currentChanged(int)"), 
                 self.updateCheckerImage)
        self.connect(self.tabWidget, 
                 QtCore.SIGNAL("currentChanged(int)"), 
                 self.updateColorImage)
        self.connect(self.tabWidget, 
                 QtCore.SIGNAL("currentChanged(int)"), 
                 self.updateShadeImage)
            
        #self.mplFigure = MyMplCanvas()
        #self.tabWidget.addTab(self.mplFigure,"MatplotLib")
        
        # Setup Right Panel (buttons and controls)
        controlsLayout = QtGui.QVBoxLayout()
        controlsPanel = QtGui.QFrame()
        controlsPanel.setLayout(controlsLayout)
        self.buttonGroup = QtGui.QGroupBox("Registration")
        buttonLayout = QtGui.QGridLayout()
        self.optimizeButton = QtGui.QPushButton(self.tr("Optimize"))
        self.optimizeAllButton = QtGui.QPushButton(self.tr("Optimize All"))
        self.optimizerResultsButton = QtGui.QPushButton(self.tr("Opt Results"))
        self.showBackgroundCheckbox = QtGui.QCheckBox()
        showBackgroundLabel = QtGui.QLabel(self.tr("DRR+X-Ray"))
        showBackgroundLabel.setAlignment(QtCore.Qt.AlignLeft)
        self.updateChartsCheckbox = QtGui.QCheckBox()
        updateChartsLabel = QtGui.QLabel(self.tr("Update Charts"))
        updateChartsLabel.setAlignment(QtCore.Qt.AlignLeft)
        self.updateChartsCheckbox.setChecked(True)
        self.showBackgroundCheckbox.setEnabled(False)
        self.optimizeButton.setEnabled(False)
        self.optimizeAllButton.setEnabled(False)
        self.optimizerResultsButton.setEnabled(False)
        self.connect(self.optimizeButton, 
                     QtCore.SIGNAL("clicked(bool)"), 
                     self.optimize)
        self.connect(self.optimizeAllButton, 
                     QtCore.SIGNAL("clicked(bool)"), 
                     self.optimizeAll)
        self.connect(self.optimizerResultsButton, 
                     QtCore.SIGNAL("clicked(bool)"), 
                     self.optimizerResults)
        self.connect(self.showBackgroundCheckbox, 
                     QtCore.SIGNAL("stateChanged(int)"), 
                     self.showBackground)
        self.metricDisplay = QtGui.QLCDNumber()
        self.metricDisplay.setSegmentStyle(self.metricDisplay.Flat)
        self.metricDisplay.setNumDigits(8)
        self.metricDisplay.setSmallDecimalPoint(True)
        self.metricDisplay.display(0.0)
        #metricLabel = QtGui.QLabel("Metric Value")
        self.metricGroup = QtGui.QGroupBox("Metric Value")
        metricLayout = QtGui.QVBoxLayout()
        self.metricGroup.setLayout(metricLayout)
        #metricLayout.addWidget(metricLabel)
        metricLayout.addWidget(self.metricDisplay)
        self.metricGroup.setCheckable(True)
        self.metricGroup.setChecked(False)
        buttonLayout.addWidget(self.optimizeButton,0,0,1,1)
        buttonLayout.addWidget(self.optimizeAllButton,1,0,1,1)
        buttonLayout.addWidget(self.optimizerResultsButton,2,0,1,1)
        #buttonLayout.setRowStretch(3,100)
        #self.buttonLayout.addWidget(self.updateCheckerButton)
        #self.buttonLayout.addWidget(self.updateColorButton)
        #self.buttonLayout.addWidget(self.metricDisplay)
        self.buttonGroup.setLayout(buttonLayout)
        
        # display Controls
        displayGroup = QtGui.QGroupBox("Display")
        displayLayout = QtGui.QGridLayout()
        displayGroup.setLayout(displayLayout)
        #self.thresholdLayout = QtGui.QHBoxLayout()
        volumeDisplayDRRRadio = QtGui.QRadioButton("DRR View")
        volumeDisplaySurfRadio = QtGui.QRadioButton("Surface View")
        volumeDisplayOutlineRadio = QtGui.QRadioButton("Outline View")
        volumeDisplayDRRRadio.setChecked(1)
        displayLayout.addWidget(volumeDisplayDRRRadio,1,0,1,1)
        displayLayout.addWidget(volumeDisplaySurfRadio,2,0,1,1)
        displayLayout.addWidget(volumeDisplayOutlineRadio,3,0,1,1)
        self.connect(volumeDisplayDRRRadio, 
                     QtCore.SIGNAL("toggled(bool)"), 
                     self.toggleVolumeDisplay) 
        
        self.displayThresholdSlider = QtGui.QSlider(QtCore.Qt.Horizontal)
        displayThresholdLabel = QtGui.QLabel("DRR Threshold:")
        displayThresholdValue = QtGui.QLabel()
        self.displayThresholdSlider.setMinimum(1)
        self.displayThresholdSlider.setMaximum(255)
        self.displayThresholdSlider.setValue(40)
        displayThresholdValue.setNum(40)
        displayLayout.addWidget(self.displayThresholdSlider,4,0,1,2)
        displayLayout.addWidget(displayThresholdLabel,5,0,1,1)
        displayLayout.addWidget(displayThresholdValue,5,1,1,1) 
        self.connect(self.displayThresholdSlider, 
                     QtCore.SIGNAL("valueChanged(int)"), 
                     displayThresholdValue.setNum)
        
        self.displayOpacitySlider = QtGui.QSlider(QtCore.Qt.Horizontal)
        displayOpacityLabel = QtGui.QLabel("DRR Opacity Level:")
        displayOpacityValue = QtGui.QLabel()
        self.displayOpacitySlider.setMinimum(0.0)
        self.displayOpacitySlider.setMaximum(100.0)
        self.displayOpacitySlider.setValue(15)
        displayOpacityValue.setNum(0.15)
        displayLayout.addWidget(self.displayOpacitySlider,6,0,1,2)
        displayLayout.addWidget(displayOpacityLabel,7,0,1,1)
        displayLayout.addWidget(displayOpacityValue,7,1,1,1) 
        def displayOpacitySliderCallback(num):
            displayOpacityValue.setNum(num/100.0)
        self.connect(self.displayOpacitySlider, 
                     QtCore.SIGNAL("valueChanged(int)"), 
                     displayOpacitySliderCallback)
        
        self.displaySurfThreshSlider = QtGui.QSlider(QtCore.Qt.Horizontal)
        displaySurfThreshLabel = QtGui.QLabel("Surf Model Thresh:")
        displaySurfThreshValue = QtGui.QLabel()
        displaySurfThreshButton = QtGui.QPushButton("Update Surf")
        self.displaySurfThreshSlider.setMinimum(0.0)
        self.displaySurfThreshSlider.setMaximum(255)
        self.displaySurfThreshSlider.setValue(80)
        displaySurfThreshValue.setNum(80)
        displayLayout.addWidget(self.displaySurfThreshSlider,8,0,1,2)
        displayLayout.addWidget(displaySurfThreshLabel,9,0,1,1)
        displayLayout.addWidget(displaySurfThreshValue,9,1,1,1)
        displayLayout.addWidget(displaySurfThreshButton,10,0,1,1) 
        # TODO: Connect to Volume threshold
        self.connect(self.displaySurfThreshSlider, 
                     QtCore.SIGNAL("valueChanged(int)"), 
                     displaySurfThreshValue.setNum)
        self.connect(displaySurfThreshButton, 
                     QtCore.SIGNAL("clicked(bool)"), 
                     self.updateSurfaceModel)
        
        self.fixedWindowLevelSlider = QtGui.QSlider(QtCore.Qt.Horizontal)
        fixedWindowLevelLabel = QtGui.QLabel("Fixed Window Level:")
        fixedWindowLevelValue = QtGui.QLabel()
        self.fixedWindowLevelSlider.setMinimum(0.0)
        self.fixedWindowLevelSlider.setMaximum(255)
        self.fixedWindowLevelSlider.setValue(255)
        fixedWindowLevelValue.setNum(255)
        displayLayout.addWidget(self.fixedWindowLevelSlider,11,0,1,2)
        displayLayout.addWidget(fixedWindowLevelLabel,12,0,1,1)
        displayLayout.addWidget(fixedWindowLevelValue,12,1,1,1)
    
        # TODO: Connect to Volume threshold
        self.connect(self.fixedWindowLevelSlider, 
                     QtCore.SIGNAL("valueChanged(int)"), 
                     fixedWindowLevelValue.setNum)
        self.connect(self.fixedWindowLevelSlider, 
                     QtCore.SIGNAL("valueChanged(int)"), 
                     self.fixedImage.SetWindowLevel)
        
        self.fixedColorLevelSlider = QtGui.QSlider(QtCore.Qt.Horizontal)
        fixedColorLevelLabel = QtGui.QLabel("Fixed Window Level:")
        fixedColorLevelValue = QtGui.QLabel()
        self.fixedColorLevelSlider.setMinimum(0.0)
        self.fixedColorLevelSlider.setMaximum(255)
        self.fixedColorLevelSlider.setValue(128)
        fixedColorLevelValue.setNum(128)
        displayLayout.addWidget(self.fixedColorLevelSlider,13,0,1,2)
        displayLayout.addWidget(fixedColorLevelLabel,14,0,1,1)
        displayLayout.addWidget(fixedColorLevelValue,14,1,1,1)
    
        # TODO: Connect to Volume threshold
        self.connect(self.fixedColorLevelSlider, 
                     QtCore.SIGNAL("valueChanged(int)"), 
                     fixedColorLevelValue.setNum)
        self.connect(self.fixedColorLevelSlider, 
                     QtCore.SIGNAL("valueChanged(int)"), 
                     self.fixedImage.SetColorLevel)
        
        
        displayLayout.addWidget(self.updateChartsCheckbox,15,0,1,1)
        displayLayout.addWidget(updateChartsLabel,15,1,1,1)
        displayLayout.addWidget(self.showBackgroundCheckbox,16,0,1,1)
        displayLayout.addWidget(showBackgroundLabel,16,1,1,1)
        displayLayout.setRowStretch(16,2)
        
        # Charts
        chartFrame = QtGui.QFrame()
        chartFrame.setFixedWidth(215)
        chartLayout = QtGui.QVBoxLayout()
        chartFrame.setLayout(chartLayout)
        self.chartCanvas = MyMplCanvas()
        self.chartCanvas.fig.subplots_adjust(bottom=0.0,top=1.0,hspace=0.0,wspace=0.0)
        self.charts = []
        for i in range(6):
            chart = self.chartCanvas.fig.add_subplot(6,1,i+1,frameon=False)
            chart.autoscale_view(tight=True)
            self.charts.append(chart)
        
        # Setup 3 3D views to display volumes from ap, ml and si angles
        #self.orthoViewer = OrthoViewer()
        #cone = get_cone()
        #self.orthoViewer.SetModel(cone)
        
        # Setup Chart Tabs
        chartTab = QtGui.QTabWidget()
        chartTab.addTab(chartFrame,'Charts')
        #chartTab.addTab(self.orthoViewer,'OrthoViews')
        chartLayout.addWidget(self.chartCanvas)
        #chartLayout.addWidget(freeViewFrame)
        
                      
        controlsLayout.addWidget(self.buttonGroup)
        controlsLayout.addWidget(displayGroup)
        controlsLayout.addWidget(self.metricGroup)
        
        # Setup bottom panel (output console)
        self.textEdit = QtGui.QTextEdit()
        self.textEdit.setReadOnly(True)
        self.textEdit.ensureCursorVisible()
        # redirect text here
        redir=RedirectText(self.textEdit)
        redirErr=RedirectErr(self.textEdit)
        sys.stdout = redir
        #sys.stderr = redirErr
        print 'pyTrack Starting...'

        # layout
        centerPanel = QtGui.QFrame()
        rightPanel = QtGui.QFrame()
        mainLayout = QtGui.QHBoxLayout()
        centerPanelLayout = QtGui.QVBoxLayout()
        centerPanelLayout.addWidget(self.tabWidget) 
        centerPanelLayout.addWidget(self.textEdit)
        centerPanel.setLayout(centerPanelLayout)
#        rightPanel = QtGui.QFrame()
#        rightPanelLayout = QtGui.QVBoxLayout()
#        rightPanelLayout.addWidget(topRightPanel)
#        rightPanelLayout.addWidget()
#        rightPanel.setLayout(rightPanelLayout)
        mainLayout.addWidget(leftPanel)
        mainLayout.addWidget(centerPanel)
        mainLayout.addWidget(chartTab)
        mainLayout.addWidget(controlsPanel)
        #mainLayout.addStretch(1)
        mainFrame.setLayout(mainLayout)
        
        # Create Actions and menus and extras
        self.createActions()
        self.createMenus()
        self.setupDecorations()
        
        # For debugging;
        #self.openProject(r'C:\Users\bryan\bryan-code\trunk\Tests\log\new_data_structure.xml')
        
        
    def newProject(self):
        print "new project not implemented"
        
            
    def openProject(self,fileName=None):
        """
        Dialog to open JointTrack xml project files.
        After the file is open, the data will must be passed to 
        loadProject to actually load the necessary components.
        """
        self.statusBar().showMessage(self.tr("Opening project file"))
        startingDir = self.cur_dir
        if (fileName is None):
            fileName = str(getOpenFileName(None,'', startingDir,''))
        if  (fileName == ''):
            print "No filename given"
            return
        # Set the default image and volume that should be displayed when the 
        # project is loaded.
        
        self.external_viewer.Open(fileName)
        self.loadProject(fileName)
        self.projectFileName = fileName
        self.setWindowTitle(os.path.basename(fileName))
        self.cur_dir = QtCore.QFileInfo(fileName).path()
        #self.windowTitle(os.path.basename(str(fileName)))
        
    def loadProject(self,filename):
        """
        Input: filename to an JointTrack xml project
        This function will load the data to the necessary components 
        (FixedImages, Volumes, ROIs, Centers, Poses) 
        """
        self.Data.load(str(filename))
        # Collect all fixed image paths from data structure
        self.fixedImage_paths = self.Data.get_unique_path_list('FixedImage')
         # Collect all fixed image paths from data structure
        self.volume_paths = self.Data.get_unique_path_list('Volume')
        self.cal_paths = self.Data.get_unique_path_list('Cal')
        
#        # initialize lists for data
#        self.fixedImage_paths = []
#        self.cal_paths = []
#        self.rois = []
#        self.volume_paths = []
##        self.staFileNames = []
##        self.volumeCenters = []
#    
#        # loop through all the fixed images in the project
#        for i,path in enumerate(fixedImage_paths):
#            if str(cal_paths[i]) not in self.cal_paths:
#                self.cal_paths.append(str(cal_paths[i]))
#                print "importing cal: %s" % str(cal_paths[i])
#            print "importing image: %s" % str(path)
#        for i,path in enumerate(volume_paths)
#            if str(volume_paths[i]) not in self.volume_paths:
#                self.volume_paths.append(str(volume_paths[i]))
#                print "importing volume: %s" % str(volume_paths[i])
             
        #self.loadFixedImageList(fixedImage_paths)
        self.imageListModel.setDataStructure(self.Data)
        self.progressBar.setMaximum(self.Data.image_count()-1)
        
        index = self.imageListModel.index(self.Data.Tree.current_image)
        self.imageListView.setCurrentIndex(index)
        self.selectImage(index)
                                     
        self.loadVolumes()
        index = self.imageListModel.index(self.Data.Tree.current_volume)
        self.volumeListView.setCurrentIndex(index)
        self.selectVolume(index) 
        
        # Load calibration files
        self.loadCals() 
        
    
    def saveProject(self):
        startingDir = self.cur_dir
        make_relative = self.makePathsRelativeAct.isChecked()
        if self.projectFileName != '':
            self.Data.save(self.projectFileName,make_relative) 
            print "Project saved to: %s" % self.projectFileName
        else:
            self.saveAsProject()
        
    
    def saveAsProject(self):
        startingDir = self.cur_dir
        fileName = str(QtGui.QFileDialog.getSaveFileName(None,'',self.projectFileName,''))
        if fileName == '':
            print "I don't understand that filename, nothing done."
            return
        make_relative = self.makePathsRelativeAct.isChecked()
        self.Data.save(fileName,make_relative) 
        self.projectFileName = fileName
        self.setWindowTitle(os.path.basename(fileName))
        # Set current directory
        self.cur_dir = QtCore.QFileInfo(fileName).path()
        print "Project saved to: %s" % fileName
    
    def openFixedImage(self):
        """
        A dialog to select fixed image(s)  (allows multi-select).  
        """
        startingDir = self.cur_dir
        if self.Data.get_path_list('FixedImage') != []:
            reply = self.appendDialog()
            if reply == "discard":
                print "Removing previous Images"
                self.fixedImage_paths = []
                #self.Data.remove_all_images()
                self.Frames = []
                self.Data.Tree.current_image = 0
            elif reply == "cancel":
                return
        fileNames = getOpenFileNames(None,'',startingDir,'')
        # Put fixed images into data structure
        self.Data.add_image_sequence(fileNames) 
#        for f in fileNames:
#            self.fixedImage_paths.append(f)
        #self.loadFixedImageList()
        self.imageListModel.setDataStructure(self.Data)
        self.progressBar.setMaximum(self.Data.image_count()-1)
        # Display the first image in the list
        self.displayFixedImage()
        # Set current directory
        self.cur_dir = QtCore.QFileInfo(self.Data.get_current_image_path()).path()
    
    def openVolume(self):
        if self.Data.get_path_list('Volume') != []:
            reply = self.appendDialog()
            if reply == "discard":
                print "Removing previous Volumes"
                self.volume_paths = []
                self.volume_nodes = []
                self.Data.remove_all_volumes()
                self.Data.Tree.current_volume = 0
                self.drr.volume_path = None
            elif reply == "cancel":
                return

        startingDir = self.cur_dir
        
        self.volume_paths.append(getOpenFileName(None,'Select a volume',
                                               startingDir,'Volumes (*.*)'))
        # Add volume to data structure
        #print "DEBUG volume filenames: %s" % self.volume_paths[-1]
        self.Data.add_volume(str(self.volume_paths[-1]))
        
        self.statusBar().showMessage(self.tr("Opening volume and creating DRR..."))
        self.loadVolumes()
        # Display the most recently opened volume
        self.displayVolume()
        # Set current directory
        self.cur_dir = QtCore.QFileInfo(self.volume_paths[-1]).path()
    
    def openSTL(self):
        """
        a hack to get STL's into the DRR.  Must Recreate functionality of 
        createVolume so that a SurfaceVolume is created and cached instead of a 
        normal Volume.  THIS IS NOT NECESSARY IT JUST CALLS 'OpenVolume'
        """
        self.openVolume()
#        if self.Data.get_path_list('Volume') != []:
#            reply = self.appendDialog()
#            if reply == "discard":
#                print "Removing previous Volumes"
#                self.volume_paths = []
#                self.volume_nodes = []
#                self.Data.remove_all_volumes()
#                self.Data.Tree.current_volume = 0
#                self.drr.volume_path = None
#            elif reply == "cancel":
#                return
#        
#            
#        startingDir = self.cur_dir
#        self.volume_paths.append(getOpenFileName(None,'Select a model',
#                                               startingDir,'Models (*.*)'))
#        
#        self.Data.add_volume(str(self.volume_paths[-1]))
#        self.statusBar().showMessage(self.tr("Opening volume and creating DRR..."))
#        self.loadVolumeList(self.volume_paths)
#        
#        volume = JT.SurfaceModel() 
#        volume.LoadSTL(str(self.volume_paths[-1]))
#        props = volume.GetProperty()
#        props.SetColor(0,0,1)
#        volume.volume_path = str(self.volume_paths[-1])
#        self.drr.ren.RemoveAllViewProps()
#        self.drr.ren.AddActor(volume)
#        self.drr.xrayCamera.SetClippingRange(0.01,3000)
#        print volume
#       
#        # Set the center of the volume
#        #self.drr.volume.SetOrientation(0,0,0)
#        trans = (np.asarray(self.drr.xrayCamera.GetPosition()) + 
#                  np.asarray(self.drr.xrayCamera.GetFocalPoint()))*0.5
#        pose = [trans[0],trans[1],trans[2],0,0,0]
#        self.drr.volume.SetPose(pose)
#        # Cache the volume data for later use
#        self.cached_volumes[str(self.volume_paths[-1])] = volume
#        print "Creating new volume: %s" % str(self.volume_paths[-1])
#        
#        # Display the most recently opened volume
#        self.displayVolume()
#        #Apply current calibration
#        self.applyCal(self.activeCal)
#        # Set current directory
#        self.cur_dir = QtCore.QFileInfo(self.volume_paths[-1]).path()
    
    
    def openCal(self):
        """
        A dialog to open a calibration file, adds it to the list of opened 
        calibrations (maybe not a good idea?).  
        """
        if self.Data.get_path_list('Cal') != []:
            reply = self.appendDialog()
            if reply == "discard":
                print "Removing previous Calibrations"
                self.cal_paths = []
                self.Data.remove_all_cals()
                self.activeCal = 0
            elif reply == "cancel":
                return

        startingDir = self.cur_dir
        
        self.cal_paths.append(getOpenFileName(None,'',startingDir,''))        
        self.loadCals()
        # Save calibration in the data structure
        self.Data.add_cal(self.cal_paths[-1])
        
        self.cur_dir = QtCore.QFileInfo(self.cal_paths[-1]).path()
        
    def loadFixedImageList(self):
        """
        THIS FUNCTION IS NOT USED?!?
        Input: a list of full file paths
        This function will create a list of stripped filenames to be displayed
        in the list view widget and also open the first image in the list.
        """
        self.imageListModel.setDataStructure(self.Data)
        self.progressBar.setMaximum(self.Data.image_count()-1)
    
    def loadVolumes(self):
        """
        This function will create a list of stripped filenames to be displayed
        in the list view widget and also open the first image in the list.
        """
        vol_list = []
        print "DEBUG: loadVolumes (filenames): %s" % str(self.volume_paths)
        for fname in self.volume_paths:
            vol_list.append(str(self.strippedName(fname)))
            print "Opening... %s" % vol_list[-1]   
        self.volumeListModel.setList(vol_list)
    
    def loadCals(self):
        """
        This function will create a list of stripped filenames to be displayed
        in the list view widget and also open the first image in the list.
        """
        cal_list = []
        for fname in self.cal_paths:
            cal_list.append(str(self.strippedName(fname)))
            print "Opening... %s" % cal_list[-1] 
            
        self.calListModel.setList(cal_list)
    
    def displayFixedImage(self):
        """
        Display a specific image into the render window
        """
        filename = self.Data.get_current_image_path()
        # Try to retrieve the fixed image from cached list of images
        try:
            fixed_image = self.cached_images[filename]
            self.fixedImage.SetImage(fixed_image)
        except:
            # if it is not there, then read it in and add to cache
            self.fixedImage.SetFileName(str(filename)) 
            self.cached_images[filename] = self.fixedImage._image
        image_size = self.fixedImage.imageSize
        # TODO: Need to do something if images are bigger than 512x512
        self.fixedImage.iren.setFixedSize(image_size[0],image_size[1])
        #self.drr.iren.setFixedSize(image_size[0],image_size[1])
        #self.drrFrame.setFixedSize(image_size[0],image_size[1])
        self.checkerImage.iren.setFixedSize(image_size[0],image_size[1])
        self.colorImage.iren.setFixedSize(image_size[0],image_size[1])
        self.shadeImage.iren.setFixedSize(image_size[0],image_size[1])
        
        # Highlight the ROI if there is one in the data structure.
        self.updateROI()

        # Set the tabwidget to actually show the whole image
        # Based on experimentation, it looks like the tabwidget has a border
        # of 2 pixels on each side and 25 pixels on the top (for tabs)
        self.tabWidget.setFixedSize(image_size[0]+4,image_size[1]+25)
        
        self.prepareFrame()
        
        self.fixedImage.renWin.Render()
        if self.drr:
            # Update Shade/checker images only if their tab is active
            if self.tabWidget.currentIndex() == 4:
                self.updateShadeImage()
            if self.tabWidget.currentIndex() == 3:
                self.updateCheckerImage()
        # Update DRR background image if necessary
        if self.showBackgroundCheckbox.isChecked():
            self.showBackground(2)
    
    def displayVolume(self):
        """
        Display a specific volume into the render window
        """
        # if this is the first volume that is loaded, create a drr
        if self.drr is None:
            self.setupDRR()
        startTime = time.time()
        current_volume = self.Data.get_current_volume_path()
        #print "DEBUG: aget_current_volume time: %f" % (startTime - time.time())
        # check to see if selected volume is already selected (do nothing)
        if current_volume == self.drr.volume_path:
            print "Volume already selected"
            self.statusBar().showMessage(self.tr("Ready..."))
            return
        # Apply calibration
        self.applyCal(self.activeCal)
        
        # check to see if selected volume has already been loaded in cache
        # This will make it faster to switch between volumes 
        # volumes are indexed by filename (full paths)
        try:
            volume = self.cached_volumes[current_volume]
            print "Loading cached volume: %s" % current_volume
            # TODO: may want to add multiple volumes to drr
            if self.multi_volume:
                self.drr.ren.AddVolume(volume)
            else:
                self.drr.SetVolume(volume)
        except KeyError:
            # Volume has not been loaded before add it to the cache and data structure
            # Create a new volume and load the data into the drr
            print "Key Error, trying to create volume"
            self.createVolume(current_volume)

        self.prepareFrame()
          
        print "active volume: %s " % (current_volume)
        self.statusBar().showMessage(self.tr("Ready..."))
        
        
    def selectImage(self,index):
        """
        This is the callback function for when an item is selected in
        the image list view.  Index is a QtListModelIndex.
        select an image from the list in the imageListModel
        """
        #self.activeImage = index.row()
        self.Data.Tree.current_image = index.row()
        print "Image #%i Selected: %s" % (int(self.Data.Tree.current_image+1),
                                         self.Data.get_current_image_path())
        self.displayFixedImage()
        self.progressBar.setValue(self.Data.Tree.current_image)
        if self.updateChartsCheckbox.isChecked():
            self.updateCharts()
        
        # Everytime you go to a new image, save the project file (just in case...)
        if self.projectFileName != None:
            # if a project name is available, use that for the backup
            # with the extension .bak
            autosave_name = self.projectFileName + '.bak'
        else:
            # If no project filename, then make up something
            autosave_name = os.path.join(self.cur_dir,'pyTrack_autosave.bak')
        self.Data.save(autosave_name)
            
#        try:
#            self.displayVolume(self.Data.Tree.current_volume)
#        except IndexError:
#            #No volume loaded yet, don't do anything
#            return
        
    def prepareFrame(self):
        """
        This function will make sure the the frame (fixed image and drr) are 
        ready to be displayed.
        """
        if self.drr == None:
            return
        try:
            pose = self.Data.get_current_pose()
            self.drr.volume.SetPose(pose)
            self.updatePoseLabel(pose)
            print "Pose set to: %s" % pose
            #self.orthoViewer.Render()
        except:
            print "No pose set for current image/volume"
            
        
        # Draw the ROI highlight box if an ROI exists for this node.
        self.updateROI()
        self.updateCenter()
        
        #print "DEBUG Fixed Image filenames: %s" % str(self.fixedImage_paths)
        #print "DEBUG Volume filenames: %s" % str(self.volume_paths)
        if isinstance(self.drr.volume,vtk.vtkVolume):
            if self.globalDisplaySettingsAct.isChecked():
                (threshold, level) = self.Data.get_global_display_settings()
                print "Using global display settings (%s,%s)" % (str(threshold),
                                                                 str(level))
            else:
                (threshold, level) = self.Data.get_display_settings()
                print "Using local display settings (%s,%s)" % (str(threshold),
                                                                str(level))
            if (threshold is None) or (level is None):
                pass
                #level = 0.15
                #threshold = 40.0
                #print "Using default display settings (%f,%f)" % (level,threshold)
                # logging.debug("Could not find display settings file, one will be created if the default values are changed")
            else:
                # try to set the DRR display using settings given
                self.drr.volume.SetOpacityLevel(level)
                self.drr.volume.SetOpacityThreshold(threshold)
        
        self.drr.Render()
    
    
    def selectVolume(self,index):
        """
        select a volume from the list in the volumeListModel
        """
        print "Volume Selected: %s" % str(self.volumeListModel.listdata[index.row()])
        #self.activeVolume = index.row()
        self.Data.Tree.current_volume = index.row()
        # Toggle whether the item is checked, now only one can be checked
        # TODO: Allow multiple volumes to be displayed at the same time.
        for i in range(len(self.volumeListModel.checked)):
            self.volumeListModel.checked[i] = 0
        self.volumeListModel.checked[index.row()] = 2
        self.volumeListModel.data(index,0)
        self.displayVolume()
        self.updateCharts()
        #
        #self.setupInteractiveReg()
    def setupDRR(self):
        """
        call once to setup the DRR.
        """
        self.drr = JT.QTDRR(self.drrFrame)
        image_size = self.fixedImage.imageSize
        self.drr.iren.setFixedSize(image_size[0],image_size[1])
        self.drr.iren.AddObserver("KeyPressEvent",self.drrKeyPress)
        #Apply current calibration
        self.applyCal(self.activeCal)
        # Initially DRR tab was disabled, only enable if everything is setup correctly
        self.tabWidget.setTabEnabled(1,True)
        self.tabWidget.setTabEnabled(2,True)
        self.tabWidget.setTabEnabled(3,True)
        self.tabWidget.setTabEnabled(4,True)
        self.buttonGroup.setEnabled(True)
        self.showBackgroundCheckbox.setEnabled(True)
        self.optimizeButton.setEnabled(True)
        self.optimizeAllButton.setEnabled(True)
        def displayThresholdSliderCallback(value):
            self.drr.volume.SetOpacityThreshold(value)
            self.drr.Render()
        def displayOpacitySliderCallback(value):
            self.drr.volume.SetOpacityLevel(value/100.0)
            self.drr.Render()
            
        self.connect(self.displayThresholdSlider, 
                     QtCore.SIGNAL("valueChanged(int)"), 
                     displayThresholdSliderCallback)
        self.connect(self.displayOpacitySlider, 
                     QtCore.SIGNAL("valueChanged(int)"), 
                     displayOpacitySliderCallback)
    
    def createVolume(self,filename):
        """
        Create a new instance of a volume and add it to the DRR
        """
        ext = filename[filename.rfind('.')+1:].lower()
        if ext == 'stl':
            volume = JT.SurfaceModel()
            volume.LoadSTL(filename)
        else:
            volume = JT.Volume2()
            volume.SetVolumeFileName(str(filename))
            self.drr.volume.SetOrientation(0,0,0)
            # Set the center of the volume
            center = (np.asarray(volume.GetCenter()) -
                      np.asarray(volume.GetPosition()))
            volume.SetOrigin(center)
        self.Data.add_current_center(volume.GetOrigin())    
        self.drr.SetVolume(volume) 
        # Cache the volume data for later use
        self.cached_volumes[str(filename)] = volume
        # Set position and orientation of volume
        # If no pose is give, place volume at origin and 50% of the way
        # to the detector
        trans = (np.asarray(self.drr.xrayCamera.GetPosition()) + 
                  np.asarray(self.drr.xrayCamera.GetFocalPoint()))*0.5
        pose = [trans[0],trans[1],trans[2],0,0,0]
        self.drr.volume.SetPose(pose)
        self.updatePoseLabel(pose)
        print "Pose set to: %s" % str(pose)
    
        print "Creating new volume: %s" % filename
    
    def selectCal(self,index):
        """
        select a calibration from the list in the calListModel
        """
        #print "Image Selected: %s" % str(self.imageListModel.listdata[index.row()])
        self.activeCal = index.row()
        self.applyCal(index.row())
         
    def applyCal(self,index):
        """
        Apply the selected calibration to the drr
        """
        print self.cal_paths[index]
        
        # pass cal file to DRR
        self.drr.LoadCalFile(str(self.cal_paths[index]))
        self.drr.xrayCamera.DoCalibration()
        self.drr.Render()
        
        # Setup the orthoviewer
        focal_point = self.drr.xrayCamera.GetFocalPoint()
        # Since camera is positioned at (0,0,0), magnitude of focal_point will
        # be the focal_length
        focal_length = JT.utils.mag(focal_point)
        #self.orthoViewer.SetFocalLength(focal_length)
        #self.orthoViewer.SetFocalPoint(focal_point)
        print "ApplyCal: FocalPoint: %s, FocalLength: %s" % (str(focal_point),
                                                             str(focal_length))
        #self.orthoViewer.Render()
        #self.displayFixedImage(index.row())
        # Redisplay volume with new calibration settings
        #self.displayVolume(self.activeVolume)
        
        
#    def loadVolumeList(self,index):
#        """
#        Input: a list of full file paths
#        This function will create a list of stripped filenames to be displayed
#        in the list view widget and also open the first image in the list.
#        """
#        
        #self.createComboCanvas()
    
#        self.fixedImage.SetFileName(str(self.fixedImage_paths[index]))
#        self.fixedImage.renWin.Render()
        
    def optimize(self,bool):
        """
        callback for optimize button push
        """
        self.statusBar().showMessage(self.tr("Optimizing..."))
        self.setupReg()
        # make sure that DRR backgound is turned off
        self.showBackgroundCheckbox.setCheckState(QtCore.Qt.Unchecked)
        self.drr.SetBackgroundImage(None)
        
        initial_pose = self.drr.volume.GetPose()
        self.Data.add_current_pose(initial_pose, type='initial')
        print "Starting pose: %s " % str(initial_pose)
        self.reg.SetInitialPose(initial_pose)
        
        # This could take a while, it would be nice if it ran in another thread.
        ########################################################################
        self.reg.Start()
        ########################################################################
        
        optimized_pose = self.reg.bestPose
        self.drr.volume.SetPose(optimized_pose)
        self.Data.add_current_pose(optimized_pose, type='current')
        self.Data.add_current_pose(optimized_pose, type='optimized')
        self.optimizerResultsButton.setEnabled(True)
        self.statusBar().showMessage(self.tr("Ready..."))
    
    def optimizeAll(self):
        """
        Iterate through each frames and volume and optimize them all!
        ROI's must be set for each image/volume pair for this to work 
        properly.
        """
        
        num_frames = self.Data.frame_count()
        self.statusBar().showMessage(self.tr("Optimizing All ...%i/%i" % 
                                             (1,num_frames)))
        #self.navHome()
        # Start optimizing on the current frame and continue until the end.
        cur_frame = self.Data.Tree.current_image
        volume_list = self.Data.get_unique_path_list('Volume')
        for i in range(cur_frame,num_frames):
            image_index = self.imageListModel.index(self.Data.Tree.current_image)
            self.imageListView.setCurrentIndex(image_index)
            self.selectImage(image_index)
            for j, volume_path in enumerate(volume_list):
                vol_index = self.volumeListModel.index(j)
                self.volumeListView.setCurrentIndex(vol_index)
                self.selectVolume(vol_index)
                print "optimizing image: %s, volume: %s" % (str(self.Data.Tree.current_image),
                                                            str(self.Data.Tree.current_volume))
                self.optimize(True) #optimize buttonpush callback
                 
            self.statusBar().showMessage(self.tr("Optimizing All ...%i/%i" % 
                                        (self.Data.Tree.current_image+1,
                                         num_frames)))
            
            self.navForward()
            
        
        self.statusBar().showMessage(self.tr("Ready..."))
            
    def optimizerResults(self):
        """
        After the optimizer has run, this function can display a report of the 
        starting and ending poses and whether there was convergence of the
        metric
        """
        if self.reg:
            self.reg.ShowFinalResults()
    
    def setupReg(self):
        if self.reg == None:
            self.reg = JT.MultiIterationRegistration()
            self.reg.StartNewLog()
            gd = JT.RegularStepGradientDescentOptimizer()
            gd.SetCostFunction(self.reg)
            self.reg.SetOptimizer(gd)
        # Get Optimizer settings from file
        try:
            opt_node = self.Data.Tree.OptimizerSettings
            opt_settings = self.Data.todict(opt_node)
            self.reg.optimizer.ApplySettings(opt_settings)
        except:
            print "could not find specified settings in project file "\
                  "using default optimizer settings"
        try:
            reg_node = self.Data.Tree.RegistrationSettings
            reg_settings = self.Data.todict(reg_node)
            self.reg.ApplySettings(reg_settings)

        except:
            print "could not find specified settings in project file "\
                  "using default registration settings"
            
        self.reg.SetFixedImage(self.fixedImage.GetImage())
        self.reg.SetDRR(self.drr)
        # If no ROI exists, Data will return None, which will result in the 
        # whole image being registered
        roi = self.Data.get_current_roi()
        self.reg.SetRegionOfInterest(roi)
    
    def setupInteractiveReg(self):
        self.interactiveReg = JT.MultiIterationRegistration()

        self.interactiveReg._plot = False
        self.interactiveReg._debug = False
        self.interactiveReg.SetImageMetric('bpc')
        self.interactiveReg.SetFixedImage(self.fixedImage.GetImage())
        self.interactiveReg.SetDRR(self.drr)
        self.reg.StartNewLog()
        self.interactiveReg.SetRegionOfInterest(None)
    
        
    def showImages(self):
        """
        Create a matplotlib figure and display the current fixed image and
        moving image.  Images will be cropped to the specified ROI.
        """
        #pyplot.ion()
        self.statusBar().showMessage(self.tr("Showing rois..."))
        self.mplFigure.fig.gca().cla()
        #self.mplFigure.fig.clf()
        vtk_image1 = self.fixedImage.GetImage()
        vtk_image2 = self.drr.GetImage()
        image1 = JT.utils.vtk2numpy(vtk_image1)
        image2 = JT.utils.vtk2numpy(vtk_image2)
        # Only display the region of interest from the image.
        roi = self.Data.Tree.Frame[self.Data.Tree.current_image]
        roi = self.rois[self.Data.Tree.current_image]
#        pylab.ion()
#        pylab.bone()
        #self.mplFigure.fig.subplots_adjust(bottom=0.05,top=0.95,
        #                                    hspace=0.1,wspace=0.1)
        # Add fixed image 
        sp1 = self.mplFigure.fig.add_subplot(211,xticks=[],yticks=[],
                                                  frameon=False)
        #sp1.cla() 
        try:
            image1 = image1[roi[0][0]:roi[1][0], roi[0][1]:roi[1][1]]
        except:
            pass
        sp1.imshow(image1,origin='lower',cmap=cm.bone)
        #print "Adding image1"
        # Add moving image, will need to be updated with each iteration
        sp2 = self.mplFigure.fig.add_subplot(212,xticks=[],yticks=[],
                                                  frameon=False)
        #sp2.cla()
        try: 
            image2 = image2[roi[0][0]:roi[1][0], roi[0][1]:roi[1][1]]
        except:
            pass
        sp2.imshow(image2,origin='lower',cmap=cm.bone)
        pyplot.draw()
        self.statusBar().showMessage(self.tr("Ready..."))
    
    def showBackground(self,state):
        #print "showbackground state: %s" % str(state)
        if state:
            self.drr.SetBackgroundImage(self.fixedImage._image)
        else:
            self.drr.SetBackgroundImage(None)

    def updateCharts(self):
        """
        Update the charts with the latest poses.
        """
        # Current frame/image
        cf = int(self.Data.Tree.current_image)
        # Current volume
        cv = int(self.Data.Tree.current_volume)
        # All of the frames may not  have poses, make those 0
        # Poses is indexed as: [frame,volume,dimension]
        poses = self.Data.get_all_poses()
        n = len(poses)
        x = range(n)
        if self.chart_lines1 == []:
            # First time, plot charts, on subsequent calls, update data (much faster)
            for dim,chart in enumerate(self.charts):
                chart.hold(False)
                self.chart_lines1.append(chart.plot(x,poses[:,:,dim],'b'))
                chart.hold(True)
                self.chart_lines2.append(chart.plot([x[cf]],[poses[cf,cv,dim]],color='#FF4500',marker='o'))
                chart.autoscale_view(tight=True)
#                rng=chart.set_ylim(np.min(poses[:,:,dim]),
#                            np.max(poses[:,:,dim]))
#                rng=chart.set_xlim(0,n)
        else:    
            for dim,chart_list in enumerate(self.chart_lines1):
                for vol,chart in enumerate(chart_list):
                    # poses series 
                    chart.set_ydata(poses[:,vol,dim])
                    ax = chart.get_axes()
                    ax.autoscale_view(tight=True)
                    rng=ax.set_ylim(np.min(poses[:,:,dim]),
                                np.max(poses[:,:,dim]))
                    #rng=ax.set_xlim(0,len(poses))
                    
            for dim,chart in enumerate(self.chart_lines2):
                # markers
                chart[0].set_xdata([x[cf]])
                chart[0].set_ydata([poses[cf,cv,dim]])
        
        self.chartCanvas.draw()
        
        self.external_viewer.UpdatePoses(poses)
        self.external_viewer.SetCurrentFrame(cf)
        
        if self.external_viewer_list != []:
            for viewer in self.external_viewer_list:
                viewer.UpdatePoses(poses)
                viewer.SetCurrentFrame(cf)
    
    def updateROI(self,roi=None):
        """
        Callback function connected to signal in roi_changed of class
        QTImageCanvas (self.fixedImage).  roi is a python list.  roi is
        saved to current node in data structure. If no roi is given
        this method will look for one in the data structure, if none is 
        found, then the roi highlighting will be turned off.
        """
        if roi==None:
            roi = self.Data.get_current_roi()
            if roi != None:
                print "ROI found in data stucture: %s" % str(roi)
                self.fixedImage.SetROI(roi)
            else:
                print "No ROI for this image/volume"
                # If there is no roi, turn off ROI box
                self.fixedImage.bboxActor.VisibilityOff()
                self.fixedImage.glyphActor.VisibilityOff()
        else:
            print "ROI updated from interaction: %s" % str(roi)
            self.Data.add_current_roi(roi)
    
    def updateCenter(self):
        """
        center is a array like data type with 3 elements.  center is
        saved to current node in data structure. If no center is given
        this method will look for one in the data structure.
        
        For now this function does not try to read the center from the project
        file, it only writes the current value to the file.
        """
        if self.drr != None:
            volume = self.drr.volume
            self.Data.add_current_center(volume.GetOrigin())
    
    def updateColorWindow(self,value):
        """
        Callback function connected to signal in QTImageCanvas.
        Save value in data structure to remember how image should be displayed.
        """
        self.Data.add_current_color_window(value)
    
    def updateWindowLevel(self,value):
        """
        Callback function connected to signal in QTImageCanvas.
        Save value in data structure to remember how image should be displayed.
        """
        self.Data.add_current_window_level(value)    

    def updateCheckerImage(self,index=2):
        #print index
        if (index != 2):
            return
        self.statusBar().showMessage(self.tr("Updating Checker Images..."))
        # Convert both images to uchar by rescaling
        image1 = self.fixedImage.GetImage()
        caster1 = JT.vtk.vtkImageShiftScale()
        caster1.SetInput(image1)
        caster1.SetOutputScalarTypeToUnsignedChar()
        caster1.SetScale(255.0/image1.GetScalarTypeMax())
        image2 = self.drr.GetImage()
        caster2 = JT.vtk.vtkImageShiftScale()
        caster2.SetInput(image2)
        caster2.SetOutputScalarTypeToUnsignedChar()
        caster2.SetScale(255.0/image2.GetScalarTypeMax())
        #Get checker image
        checker = self.checkerImage.CheckerImages(caster1.GetOutput(),
                                                  caster2.GetOutput())
        # Set the checker image
        self.checkerImage.SetImage(checker)
        self.checkerImage.iren.Render()
        self.statusBar().showMessage(self.tr("Ready..."))
    
    def updateColorImage(self,index=3):
        #print index
        if (index != 3):
            return
        self.statusBar().showMessage(self.tr("Updating Color Images..."))
        # Convert both images to uchar by rescaling
        image1 = self.fixedImage.GetImage()
        caster1 = JT.vtk.vtkImageShiftScale()
        caster1.SetInput(image1)
        caster1.SetOutputScalarTypeToUnsignedChar()
        caster1.SetScale(250.0/image1.GetScalarTypeMax())
        image2 = self.drr.GetImage()
        caster2 = JT.vtk.vtkImageShiftScale()
        caster2.SetInput(image2)
        caster2.SetOutputScalarTypeToUnsignedChar()
        caster2.SetScale(255.0/image2.GetScalarTypeMax())
        color = self.colorImage.OverlayImages(caster2.GetOutput(),
                                                caster1.GetOutput())
        #print color
        #Get color image
        self.colorImage.SetImage(color)
        self.statusBar().showMessage(self.tr("Ready..."))
    
    def updateShadeImage(self,index=4):
        """
        Implements a vtk windowshade widget to assist in compairison between
        the DRR and fixed fluoro images.
        """
        if (index != 4):
            return
        self.statusBar().showMessage(self.tr("Updating Windowshade Images..."))
        image1 = self.fixedImage.GetImage()
        image2 = self.drr.GetImage()
        
        size = image1.GetDimensions()
        self.shadeImage.iren.setFixedSize(size[0],size[1])
        
        # Send the images to windowshade filter
        self.shadeImage.SetImage1(image1)
        self.shadeImage.SetImage2(image2)
        
        self.shadeImage.iren.Initialize()
        #self.shadeImage.iren.Render()
        self.shadeImage.iren.Start()
        self.shadeImage.renWin.Render()
        self.shadeImage.iren.Render()
        
        self.statusBar().showMessage(self.tr("Ready..."))
    
    def updateSurfaceModel(self,bool):
        """
        Create a surface model of the selected volume data using marching cubes
        algorithm and the threshold selected in Surface Threshold slider. 
        """
        self.statusBar().showMessage(self.tr("Updating Surface Model..."))
        try:
            self.drr.ren.RemoveActor(self.surf)
            print "Removing previous surface model"
        except:
            print "Could not remove previous surface model (maybe there wasn't one?)"
            
            
        self.surf = JT.SurfaceModel()
        self.surf.SetInput(self.drr.volume._volImageData)
        threshold = self.displaySurfThreshSlider.value()
        print "Updating surface model with threshold: %i" % threshold
        self.surf.CreateIsoSurface(threshold)
        self.surf.SetPosition(self.drr.volume.GetPosition())
        self.surf.SetOrientation(self.drr.volume.GetOrientation())
        # Allow surf to be transformed to match the drr
        def SyncProps(obj,event):
            self.drr.Render()
            if self.drr.volume == self.drr.style._PickedActor:
                self.surf.SetPosition(self.drr.volume.GetPosition())
                self.surf.SetOrientation(self.drr.volume.GetOrientation())
            elif self.surf == self.drr.style._PickedActor:
                self.drr.volume.SetPosition(self.surf.GetPosition())
                self.drr.volume.SetOrientation(self.surf.GetOrientation())
            
        self.drr.iren.AddObserver("KeyPressEvent",SyncProps)
        self.drr.iren.AddObserver("MouseMoveEvent",SyncProps)
        self.drr.ren.AddActor(self.surf)
        self.drr.Render()
        self.statusBar().showMessage(self.tr("Ready..."))

    def toggleVolumeDisplay(self,checked):
        """
        A callback function for surface display radio button.  If checked, 
        will make surface visible and drr hidden
        """
        if checked:
            print "Showing DRR, hiding surface"
            self.drr.volume.SetVisibility(1)
            self.surf.SetVisibility(0)
            self.drr.Render()
        else:
            print "Showing surface, hiding DRR"
            self.drr.volume.SetVisibility(0)
            self.surf.SetVisibility(1)
            self.drr.Render()
        
    def plotKinematics(self):
        """
        This will pass the current data structure to kinematics module.
        The user will be prompted to select which two models that should be 
        selected for plotting.
        """
        volume_paths = self.Data.get_unique_path_list('Volume')
        prompt = "Joint angles between which volumes? (eg. 12 or 32): \n"
        for i,path in enumerate(volume_paths):
            prompt+="    %i - %s\n" % (i,str(path))
        raw_choice = QtGui.QInputDialog.getText(None,"Choose Models",prompt)
        if raw_choice[1] == False:
            return
        choice = str(raw_choice[0])
        if len(choice)!=2:
            return
        xml_string = self.Data.tostring()
        JT.Kinematics.plot_results_from_string(xml_string,choice)
    
    def plotMetric(self):
        """
        This function will call the evaluate metric method in the Metric class
        and pass it the current registration and pose (they must be already 
        setup)
        """
        if self.reg == None:
            self.setupReg()
        n_pts = int(QtGui.QInputDialog.getText(None,"Number of Points",
                                           "How many points would you like to evaluate?")[0])
        stdev = float(QtGui.QInputDialog.getText(None,"Standard Deviation",
                                           "How big of a range to explore?")[0])
        suggested_pose = JT.Metric.EvaluateMetric(self.reg, 
                                                  self.Data.get_current_pose(),
                                                  n_pts,stdev)
#        reply = QtGui.QMessageBox.question(self, self.tr("Update Kinematics"),
#                           self.tr("A new pose has been suggested.\n"
#                              "Do you want to try it out?"),
#                           QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)
#        if reply == QtGui.QMessageBox.Yes:
#            "Pose updated to suggested pose: %s" % str(suggested_pose)
#            self.Data.add_current_pose(suggested_pose)
            

    def toggleVolumeDisplayDRR(self,checked):
        """
        A callback function for surface display radio button.  If checked, 
        will make surface visible and drr hidden
        """
        if checked:
            print "Showing DRR, hiding surface"
            self.drr.volume.SetVisibility(1)
            self.surf.SetVisibility(0)
        else:
            print "Showing surface, hiding DRR"
            self.drr.volume.SetVisibility(0)
            self.surf.SetVisibility(1)

    def toggleVolumeDisplaySurf(self,checked):
        """
        A callback function for surface display radio button.  If checked, 
        will make surface visible and drr hidden
        """
        if checked:
            print "Showing Surface, hiding DRR"
            self.drr.volume.SetVisibility(0)
            self.surf.SetVisibility(1)
        else:
            print "Showing DRR, hiding surface"
            self.drr.volume.SetVisibility(1)
            self.surf.SetVisibility(0)

    def toggleVolumeDisplayOutline(self,checked):
        """
        A callback function for surface display radio button.  If checked, 
        will make surface visible and drr hidden
        """
        print "Outline not implemented"
        
        
    def drrKeyPress(self,obj,event):
        """
        Callback for key presses in the DRR render window
        """
        
        keynum = self.drr.iren._key
        if ((keynum == LEFT) or (keynum == UP) or (keynum == RIGHT) or (keynum == DOWN)):
            # Save the current pose to the data structure every time an arrow key is pressed
            #startTime= time.time()
            self.Data.add_current_pose(self.drr.volume.GetPose(),'current')
            #print "DEBUG: add_current_pose time: %f" % (startTime - time.time())
            self.updatePoseLabel(self.drr.volume.GetPose())
            #startTime= time.time()
            #print "DEBUG: update pose label times: %f" % (startTime - time.time())
            # Updating the charts takes almost 1 second and drastically slows
            # down keyboard interaction, but i think it is worth it.
            if self.updateChartsCheckbox.isChecked():
                self.updateCharts()
            #print "DEBUG: update charts: %f" % (startTime - time.time())
            # This probably doesn't need to be done every time a key is pressed
            #startTime= time.time()
#            self.Data.add_display_settings(self.drr.volume._otfThreshold,
#                                           self.drr.volume._otfLevel)
            #print "DEBUG: add_display_timings: %f" % (startTime - time.time())
            # Only proceed if metric group is checked event is an arrow key 
            if self.metricGroup.isChecked():
                self.CalculateInteractiveMetric()
        
        if (keynum == 76) or (keynum == 84): # l or t
            self.Data.add_display_settings(self.drr.volume._otfThreshold,
                                           self.drr.volume._otfLevel)
                   
    def shadeKeyPress(self,obj,event):
        """
        Allow control of the DRR from the windowshade tab
        """
        keynum = self.shadeImage.iren._key
        if ((keynum == LEFT) or (keynum == UP) or 
                (keynum == RIGHT) or (keynum == DOWN)):
            if (self.shadeImage.iren._shift):
                self.KeyboardZTransRotate(keynum)
            elif (self.shadeImage.iren._ctrl):
                self.KeyboardRotate(keynum)
            else:
                self.KeyboardTranslate(keynum)
            # Save the current pose to the data structure every time an arrow key is pressed
            self.Data.add_current_pose(self.drr.volume.GetPose(),'current')
            self.updatePoseLabel(self.drr.volume.GetPose())
            # Only proceed if metric group is checked event is an arrow key 
            if self.metricGroup.isChecked():
                self.CalculateInteractiveMetric()
        self.updateShadeImage()
    
    def checkerKeyPress(self,obj,event):
        """
        Allow control of the DRR from the checker tab
        """
        keynum = self.checkerImage.iren._key
        if ((keynum == LEFT) or (keynum == UP) or 
                (keynum == RIGHT) or (keynum == DOWN)):
            if (self.checkerImage.iren._shift):
                self.KeyboardZTransRotate(keynum)
            elif (self.checkerImage.iren._ctrl):
                self.KeyboardRotate(keynum)
            else:
                self.KeyboardTranslate(keynum)
            # Save the current pose to the data structure every time an arrow key is pressed
            self.Data.add_current_pose(self.drr.volume.GetPose(),'current')
            self.updatePoseLabel(self.drr.volume.GetPose())
            # Only proceed if metric group is checked event is an arrow key 
            if self.metricGroup.isChecked():
                self.CalculateInteractiveMetric()
        self.updateCheckerImage()
#================================================================================
# These next 3 functions are for manipulating a drr, while viewing a 2D image
#================================================================================   
    def KeyboardTranslate(self,keynum):
        #print "Keyboard translate"
        cam = self.drr.xrayCamera
        # Get the view up (y) and view right (x) vectors
        view_up = plane_normal = view_right = plane_normal = np.zeros(3)
        cam.OrthogonalizeViewUp()
        view_up = np.asarray(cam.GetViewUp())
        view_up = view_up/JT.utils.mag(view_up)
        dir_proj = cam.GetDirectionOfProjection()
        plane_normal[0] = -dir_proj[0]
        plane_normal[1] = -dir_proj[1]
        plane_normal[2] = -dir_proj[2]
        view_right = np.cross(view_up, plane_normal)
        view_right = view_right/JT.utils.mag(view_right)

        # GetLength gets the length of the diagonal of the bounding box
        stepsize = self.drr.volume.GetLength() / 200 * self._keyboardStepSize
        translation = np.zeros(3)
        #keynum = self.shadeImage.iren._key
        if not(keynum):
            return
        if (keynum == LEFT):
            # LEFT
            translation[0] = -stepsize * view_right[0]
            translation[1] = -stepsize * view_right[1]
            translation[2] = -stepsize * view_right[2]
        elif (keynum == RIGHT):
            # RIGHT
            translation[0] = stepsize * view_right[0]
            translation[1] = stepsize * view_right[1]
            translation[2] = stepsize * view_right[2]
        elif (keynum == UP):
            # UP
            translation[0] = stepsize * view_up[0]
            translation[1] = stepsize * view_up[1]
            translation[2] = stepsize * view_up[2]
        elif (keynum == DOWN):
            # DOWN
            translation[0] = -stepsize * view_up[0]
            translation[1] = -stepsize * view_up[1]
            translation[2] = -stepsize * view_up[2]
        else:  #not a valid key press
            return
        #print translation
        #print self.drr.volume
        self.drr.volume.AddPosition(translation)
        #If there is a surface model update it as well
        try:
            self.surf.AddPosition(translation)
        except AttributeError:
            pass
        
        
    def KeyboardRotate(self,keynum):
        #print "Keyboard rotate"
        # rotate using the local coordinates, about the origin
        #keynum = self.iren._key
        cam = self.drr.xrayCamera
        yaxis = cam.GetViewUp()
        zaxis = cam.GetViewPlaneNormal()
        xaxis = np.cross(yaxis,zaxis)
        if not(keynum):
            return
        if (keynum == LEFT):
            # World Y axes
            self.drr.volume.RotateWXYZ(-self._rotStepSize * \
                                         self._keyboardStepSize,\
                                         yaxis[0],yaxis[1],yaxis[2])
            #If there is a surface model update it as well
            try:
                self.surf.RotateWXYZ(-self._rotStepSize * \
                                         self._keyboardStepSize,\
                                         yaxis[0],yaxis[1],yaxis[2])
            except AttributeError:
                pass
        elif (keynum == RIGHT):
            self.drr.volume.RotateWXYZ(self._rotStepSize * \
                                         self._keyboardStepSize,\
                                         yaxis[0],yaxis[1],yaxis[2])
            #If there is a surface model update it as well
            try:
                self.surf.RotateWXYZ(-self._rotStepSize * \
                                         self._keyboardStepSize,\
                                         yaxis[0],yaxis[1],yaxis[2])
            except:
                pass
        elif (keynum == UP):
            self.drr.volume.RotateWXYZ(-self._rotStepSize * \
                                         self._keyboardStepSize,\
                                         xaxis[0],xaxis[1],xaxis[2])
            #If there is a surface model update it as well
            try:
                self.surf.RotateWXYZ(-self._rotStepSize * \
                                         self._keyboardStepSize,\
                                         xaxis[0],xaxis[1],xaxis[2])
            except:
                pass
        elif (keynum == DOWN):
            self.drr.volume.RotateWXYZ(self._rotStepSize * \
                                         self._keyboardStepSize,\
                                         xaxis[0],xaxis[1],xaxis[2])
            #If there is a surface model update it as well
            try:
                self.surf.RotateWXYZ(self._rotStepSize * \
                                         self._keyboardStepSize,\
                                         xaxis[0],xaxis[1],xaxis[2])
            except:
                pass
        else:  #not a valid key press
            return
    
    def KeyboardZTransRotate(self,keynum):
        #print "Keyboard ztrans rotate"
        cam = self.drr.xrayCamera
        yaxis = cam.GetViewUp()
        zaxis = cam.GetViewPlaneNormal()
        xaxis = np.cross(yaxis,zaxis)
        #cam.GetDirectionOfProjection()
        #As of vtk5.0.3, inferred from the source code, direction of
        # projection is just the direction from camera position to
        # its focal point (before any oblique or ViewShear operation).
        # Therefore it is our Z(out-of-plane) axis.
        plane_normal = np.zeros(3)
        dir_proj = cam.GetDirectionOfProjection()
        plane_normal[0] = -dir_proj[0]
        plane_normal[1] = -dir_proj[1]
        plane_normal[2] = -dir_proj[2]
        plane_normal = plane_normal/JT.utils.mag(plane_normal)

        # GetLength gets the length of the diagonal of the bounding box
        stepsize =self.drr.volume.GetLength() / 20 * self._keyboardStepSize
        translation = np.zeros(3)
        scale = [1, 1, 1]
        #keynum = self.shadeImage.iren._key
        if not(keynum):
            return
        if (keynum == LEFT):
            #spin counterclockwise
            self.drr.volume.RotateWXYZ(self._rotStepSize * \
                                         self._keyboardStepSize,\
                                         zaxis[0],zaxis[1],zaxis[2])
            #If there is a surface model update it as well
            try:
                self.surf.RotateWXYZ(self._rotStepSize * \
                                         self._keyboardStepSize,\
                                         zaxis[0],zaxis[1],zaxis[2])
            except:
                pass
        elif (keynum == RIGHT):
            #spin clockwise
            self.drr.volume.RotateWXYZ(-self._rotStepSize * \
                                         self._keyboardStepSize,\
                                         zaxis[0],zaxis[1],zaxis[2])
            #If there is a surface model update it as well
            try:
                self.surf.RotateWXYZ(-self._rotStepSize * \
                                         self._keyboardStepSize,\
                                         zaxis[0],zaxis[1],zaxis[2])
            except:
                pass
        elif (keynum == UP):
            #bring closer
            translation[0] = stepsize * plane_normal[0]
            translation[1] = stepsize * plane_normal[1]
            translation[2] = stepsize * plane_normal[2]
            self.drr.volume.AddPosition(translation)
            #If there is a surface model update it as well
            try:
                self.surf.AddPosition(translation)
            except:
                pass
        elif (keynum == DOWN):
            #push further
            translation[0] = -stepsize * plane_normal[0]
            translation[1] = -stepsize * plane_normal[1]
            translation[2] = -stepsize * plane_normal[2]
            self.drr.volume.AddPosition(translation)
            #If there is a surface model update it as well
            try:
                self.surf.AddPosition(translation)
            except:
                pass
        else:  #not a valid key press
            return

        self.drr.ren.ResetCameraClippingRange()
            
    def CalculateInteractiveMetric(self):
        if self.interactiveReg == None:
            self.setupInteractiveReg()
            #
        pose = self.drr.volume.GetPose()
        value = self.interactiveReg.GetValue(pose)
        self.metricDisplay.display(value)
        print "Metric:,%4.4f, Pose[mm,deg]:, %4.2f, %4.2f, %4.2f, %4.2f, %4.2f, %4.2f" %\
                   (value,pose[0],pose[1],pose[2],
                    np.degrees(pose[3]),np.degrees(pose[4]),np.degrees(pose[5]))
            #print "DRR key pressed: %s" % event
               
    def save(self):
        self.statusBar().showMessage(self.tr("Invoked <b>File|Save</b>"))
    
    def close_(self):
        # Confirm???
        
        self.statusBar().showMessage(self.tr("Exiting..."))
        # To close renderwindows cleanly, need to disconnect interactors first
        try:
            self.fixedImage.renWin.SetInteractor(None)
        except:
            pass
        try:
            self.drr.renWin.SetInteractor(None)
        except:
            pass
        print 'Exiting...'
        sys.stdout = sys.__stdout__
        sys.stderr = sys.__stderr__
        self.close()
        
    def print_(self):
        self.statusBar().showMessage(self.tr("Invoked <b>File|Print</b>"))
    
    def undo(self):
        self.statusBar().showMessage(self.tr("Invoked <b>Edit|Undo</b>"))
    
    def restore(self):
        self.statusBar().showMessage(self.tr("Invoked <b>Edit|Redo</b>"))
        # This is very simple, restores all data since last backup, could be 
        # very bad if backup is old 
        self.Data.restore_backup()
        
    def cut(self):
        self.statusBar().showMessage(self.tr("Invoked <b>Edit|Cut</b>"))
        self.Data.create_backup()
        # Delete pose for current image/volume node.
        self.Data.delete_current_pose()
    
    def copy(self):
        self.statusBar().showMessage(self.tr("Invoked <b>Edit|Copy</b>"))
        self.current_pose = self.Data.get_current_pose()
    
    def pastePrevious(self):
        """
        Copy the pose from the previous frame (for the current volume) and 
        paste it to the current frame
        """
        # Current frame/image
        cf = int(self.Data.Tree.current_image)
        if cf == 0:
            print "Cannot copy previous for the the first frame"
            return
        # Current volume
        cv = int(self.Data.Tree.current_volume)
        # All of the frames may not  have poses, make those 0
        # Poses is indexed as: [frame,volume,dimension]
        poses = self.Data.get_all_poses()
        self.Data.add_current_pose(poses[cf-1,cv,:])
        self.drr.volume.SetPose(self.Data.get_current_pose())
        self.updatePoseLabel(self.Data.get_current_pose())
        self.drr.Render()
        #self.orthoViewer.Render()
        self.updateCharts()
        
    def pasteNext(self):
        """
        Copy the pose from the previous frame (for the current volume) and 
        paste it to the current frame
        """
        # Current frame/image
        cf = int(self.Data.Tree.current_image)
        # Current volume
        cv = int(self.Data.Tree.current_volume)
        # All of the frames may not  have poses, make those 0
        # Poses is indexed as: [frame,volume,dimension]
        poses = self.Data.get_all_poses()
        if cf == (len(poses)-1):
            print "Cannot copy next for the the last frame"
            return
        self.Data.add_current_pose(poses[cf+1,cv,:])
        self.drr.volume.SetPose(self.Data.get_current_pose())
        self.updatePoseLabel(self.Data.get_current_pose())
        self.drr.Render()
        #self.orthoViewer.Render()
        self.updateCharts()
    
    def copyROI(self):
        """
        Copy the current image/volume ROI to memory to paste to other images
        """
        self._roi = self.Data.get_current_roi()
        print "Copied ROI: %s" % str(self._roi)
    
    def pasteROI(self):
        """
        Copy the current image/volume ROI to memory to paste to other images
        """
        if self._roi != None:
            self.updateROI(self._roi)
            self.fixedImage.SetROI(self._roi)
        else:
            print "No ROI in clipboard to paste."
    
    def pasteROIAll(self):
        """
        Copy the current image/volume ROI to memory to paste to all images
        """
        if self._roi != None:
            self.updateROI(self._roi)
            volume_path = self.volume_paths[int(self.Data.Tree.current_volume)]
            print "Volume: ", volume_path
            self.Data.add_roi_all_frames(volume_path, self._roi)
            self.fixedImage.SetROI(self._roi)
            print "ROI added to all frames."
        else:
            print "No ROI in clipboard to paste."
        
    def extrapolateNext(self):
        print "Not implemented"
        
    def navForward(self):
        #print "Forward navigation"
        if (self.Data.Tree.current_image+1) < self.Data.frame_count():
            self.Data.Tree.current_image += 1
        else:
            print "Nav Forward: can not go forward from last frame."
            return
            
        index = self.imageListModel.index(self.Data.Tree.current_image)
        self.imageListView.setCurrentIndex(index)
        self.selectImage(index)
                    
    def navBack(self):
        #print "Back navigation"
        if self.Data.Tree.current_image != 0:
            self.Data.Tree.current_image -= 1
        else:
            print "Nav Back: can not go back from first frame."
            return
    
        index = self.imageListModel.index(self.Data.Tree.current_image)
        self.imageListView.setCurrentIndex(index)
        self.selectImage(index)
    
    def navHome(self):
        #print "Home navigation"
        self.Data.Tree.current_image = 0
        index = self.imageListModel.index(self.Data.Tree.current_image)
        self.imageListView.setCurrentIndex(index)
        self.selectImage(index)
        
    def navEnd(self):
        #print "End navigation"
        nimages = self.Data.image_count()
        self.Data.Tree.current_image = nimages-1
        index = self.imageListModel.index(nimages-1)
        self.imageListView.setCurrentIndex(index)
        self.selectImage(index)
        
    def convertmCal(self):
        self.statusBar().showMessage(self.tr("Coverting m cal"))
        filename = getOpenFileName(None,'',self.cur_dir,'')
        JT.utils.ConvertMcal(filename)
        self.statusBar().showMessage(self.tr("Ready..."))
        
    def makePathsRelative(self,b=False):
        print "relative paths", b
        self.make_paths_relative = b
        
    def globalDisplaySettings(self):
        print "global display: "
        self.use_global_display_settings = self.globalDisplaySettingsAct.isChecked() 
    
    
    def open3DViewer(self):
        """
        Opens a ModelAnimator window and connects it to the current project
        file (saves a temp version and loads into Animator).  Multiple windows
        can be open at the same time to simultaneously display different angles.
        """
        self.statusBar().showMessage(self.tr("Preparing 3D Viewer..."))
        print "This feature is buggy, sorry."
#        temp_filename = time.strftime('%Y%m%d_%H%M%S')+'.tmp'
#        print "Temp file saved to: %s" % (os.path.join(os.getcwd(),temp_filename))
#        
#        # Save the file, do not use the Data Structures built in save method
#        fid = open(str(temp_filename),'w')
#        xml_string = self.Data.tostring()
#        fid.write(xml_string)
#        fid.close()
#        
#        # Create Animate Window to display 3D results and add it to the list
#        self.external_viewer_list.append(JT.Animate())
#        self.external_viewer_list[-1].Open(temp_filename)
#        self.updateCharts()
        self.statusBar().showMessage(self.tr("Ready..."))

    def registrationSettings(self):
        """
        TODO: combine these three settings methods into one and 
        access using etree find('*Settings')
        """
        try:
            # You wold only want this the first time???
            s_node = self.Data.Tree.RegistrationSettings
            saved_settings = self.Data.todict(s_node)
        except:
            print "Could not find specified settings in project file"
            saved_settings = {}
        
        if self.reg == None:
            self.setupReg()
            
        settings = self.reg.settings
        for key in saved_settings:
            settings[key] = saved_settings[key]

        dlg = DialogBuilder(settings,'Registration Settings')
        ret = dlg.exec_()
        print dlg.data
        # Delete the old settings and apply new ones
        self.Data.Tree.RegistrationSettings.clear()
        # Save the new settings in the data structure
        self.Data.fromdict(self.Data.Tree.RegistrationSettings, dlg.data)
        # Apply the settings back to the metric
        self.reg.ApplySettings(dlg.data)

    def metricSettings(self):
        try:
            s_node = self.Data.Tree.MetricSettings
            saved_settings = self.Data.todict(s_node)
        except:
            print "Could not find specified settings in project file"
            saved_settings = {}
        
        if self.reg == None:
            self.setupReg()
            
        # Get the settings from registration class and 
        # populate from saved settings
        settings = self.reg.metric_settings
        for key in saved_settings:
            settings[key] = saved_settings[key]
            
        dlg = DialogBuilder(settings,'Metric Settings')
        ret = dlg.exec_()
        print dlg.data
        # Delete the old settings and apply new ones
        self.Data.Tree.MetricSettings.clear
        self.Data.fromdict(self.Data.Tree.MetricSettings, dlg.data)
        # Apply the settings back to the metric
        self.reg.ApplyMetricSettings(dlg.data)
        
    def optimizerSettings(self):
        try:
            s_node = self.Data.Tree.OptimizerSettings
            saved_settings = self.Data.todict(s_node)
        except:
            print "Could not find specified settings in project file"
            saved_settings = {}
        
        if self.reg == None:
            self.setupReg()
        
        # Get the settings from registration class and 
        # populate from saved settings
        settings = self.reg.optimizer.settings
        for key in saved_settings:
            settings[key] = saved_settings[key]

        dlg = DialogBuilder(settings,'Optimizer Settings')
        ret = dlg.exec_()
        print dlg.data
        # Delete the old settings and apply new ones
        self.Data.Tree.OptimizerSettings.clear
        self.Data.fromdict(self.Data.Tree.OptimizerSettings, dlg.data)
        # Apply the settings back to the metric
        self.reg.optimizer.ApplySettings(dlg.data)
        
    def strippedName(self,fullFileName):
        """
        Returns only the user-friendly version filename from a full file path
        """
        return QtCore.QFileInfo(fullFileName).fileName()
    
    def appendDialog(self):
        """
        Returns true if the user wants to append the data, false if the old
        data should be discarded
        """
        msgbox=QtGui.QMessageBox()
        msgbox.setText("Append or Discard")
        msgbox.setInformativeText("Do you want to save current items?")
        msgbox.setStandardButtons(msgbox.Save|msgbox.Discard|msgbox.Cancel)
        msgbox.setDefaultButton(msgbox.Save)
        reply = msgbox.exec_()
        if reply == msgbox.Save:
            return "append"
        elif reply == msgbox.Discard:
            return "discard"
        else:
            return "cancel"
                                       
    def about(self):
        self.statusBar().showMessage(self.tr("Invoked <b>Help|About</b>"))
        QtGui.QMessageBox.about(self, self.tr("About Menu"),
                self.tr("pyTrack written by bryan conrad 2009"))
               
    def aboutQt(self):
        self.statusBar().showMessage(self.tr("Invoked <b>Help|About Qt</b>"))
    
    def createActions(self):
        self.newProjectAct = QtGui.QAction(self.tr("&New Project"), self)
        self.newProjectAct.setShortcut(self.tr("Ctrl+N"))
        self.newProjectAct.setStatusTip(self.tr("Create a new project"))
        self.connect(self.newProjectAct, QtCore.SIGNAL("triggered()"), 
                     self.newProject)

        self.openProjectAct = QtGui.QAction(self.tr("&Open Project..."), self)
        self.openProjectAct.setShortcut(self.tr("Ctrl+O"))
        self.openProjectAct.setStatusTip(self.tr("Open an existing project"))
        self.connect(self.openProjectAct, QtCore.SIGNAL("triggered()"), 
                     self.openProject)

        self.saveProjectAct = QtGui.QAction(self.tr("&Save Project"), self)
        self.saveProjectAct.setShortcut(self.tr("Ctrl+S"))
        self.saveProjectAct.setStatusTip(self.tr("Save the project to disk"))
        self.connect(self.saveProjectAct, QtCore.SIGNAL("triggered()"), self.saveProject)
        
        self.saveAsProjectAct = QtGui.QAction(self.tr("&Save Project As..."), self)
        self.saveAsProjectAct.setShortcut(self.tr("Ctrl+Shift+S"))
        self.saveAsProjectAct.setStatusTip(self.tr("Save the project to disk"))
        self.connect(self.saveAsProjectAct, QtCore.SIGNAL("triggered()"), self.saveAsProject)
        
        self.openFixedImageAct = QtGui.QAction(self.tr("Open &Fixed Images..."), self)
        self.openFixedImageAct.setShortcut(self.tr("Ctrl+F"))
        self.openFixedImageAct.setStatusTip(self.tr("Open an fixed image"))
        self.connect(self.openFixedImageAct, QtCore.SIGNAL("triggered()"), 
                     self.openFixedImage)
        
        self.openVolumeAct = QtGui.QAction(self.tr("Open Volume..."), self)
        self.openVolumeAct.setStatusTip(self.tr("Open a volume"))
        self.connect(self.openVolumeAct, QtCore.SIGNAL("triggered()"), 
                     self.openVolume)
        
        self.openSTLAct = QtGui.QAction(self.tr("Open STL..."), self)
        self.openSTLAct.setStatusTip(self.tr("Open an STL Model"))
        self.connect(self.openSTLAct, QtCore.SIGNAL("triggered()"), 
                     self.openSTL)
        
        self.openCalAct = QtGui.QAction(self.tr("Open Calibration..."), self)
        self.openProjectAct.setStatusTip(self.tr("Open a Calibration"))
        self.connect(self.openCalAct, QtCore.SIGNAL("triggered()"), 
                     self.openCal)
        
        self.convertCalAct = QtGui.QAction(self.tr("Convert m-Calibration..."), self)
        self.convertCalAct.setStatusTip(self.tr("Open an m-Calibration"))
        self.connect(self.convertCalAct, QtCore.SIGNAL("triggered()"), 
                     self.convertmCal)
        
        self.open3DViewerAct = QtGui.QAction(self.tr("Open 3D viewer..."), self)
        self.open3DViewerAct.setShortcut(self.tr("Ctrl+E"))
        self.open3DViewerAct.setStatusTip(self.tr("Open an External 3D viewer"))
        self.connect(self.open3DViewerAct, QtCore.SIGNAL("triggered()"), 
                     self.open3DViewer)

        self.exitAct = QtGui.QAction(self.tr("E&xit"), self)
        self.exitAct.setShortcut(self.tr("Ctrl+Q"))
        self.exitAct.setStatusTip(self.tr("Exit the application"))
        self.connect(self.exitAct, QtCore.SIGNAL("triggered()"), self.close_)
        
        self.navForwardAct = QtGui.QAction(self.tr("&Forward"), self)
        self.navForwardAct.setShortcut(self.tr("."))
        self.navForwardAct.setStatusTip(self.tr("Move Forward One Frame"))
        self.connect(self.navForwardAct, QtCore.SIGNAL("triggered()"), self.navForward)

        self.navBackAct = QtGui.QAction(self.tr("&Back"), self)
        self.navBackAct.setShortcut(self.tr(","))
        self.navBackAct.setStatusTip(self.tr("Move Back One Frame"))
        self.connect(self.navBackAct, QtCore.SIGNAL("triggered()"), self.navBack)
        
        self.navHomeAct = QtGui.QAction(self.tr("&Home"), self)
        self.navHomeAct.setShortcut(self.tr("Alt+,"))
        self.navHomeAct.setStatusTip(self.tr("Move To First Frame"))
        self.connect(self.navHomeAct, QtCore.SIGNAL("triggered()"), self.navHome)

        self.navEndAct = QtGui.QAction(self.tr("&End"), self)
        self.navEndAct.setShortcut(self.tr("Alt+."))
        self.navEndAct.setStatusTip(self.tr("Move to the last frame"))
        self.connect(self.navEndAct, QtCore.SIGNAL("triggered()"), self.navEnd)

#        self.undoAct = QtGui.QAction(self.tr("&Undo"), self)
#        self.undoAct.setShortcut(self.tr("Ctrl+Z"))
#        self.undoAct.setStatusTip(self.tr("Undo the last pose operation"))
#        self.connect(self.undoAct, QtCore.SIGNAL("triggered()"), self.undo)
#        
#        self.restoreAct = QtGui.QAction(self.tr("&Redo"), self)
#        self.restoreAct.setShortcut(self.tr("Ctrl+Y"))
#        self.restoreAct.setStatusTip(self.tr("Restore the backup data, BE CAREFUL"))
#        self.connect(self.restoreAct, QtCore.SIGNAL("triggered()"), self.restore)

        self.cutAct = QtGui.QAction(self.tr("Cu&t"), self)
        self.cutAct.setShortcut(self.tr("Ctrl+X"))
        self.cutAct.setStatusTip(self.tr("Delete the current frame's pose"))
        self.connect(self.cutAct, QtCore.SIGNAL("triggered()"), self.cut)

        self.pastePrevAct = QtGui.QAction(self.tr("Paste &Previous"), self)
        self.pastePrevAct.setShortcut(self.tr("Ctrl+V"))
        self.pastePrevAct.setStatusTip(self.tr("Copy the previous pose to the "
                                          "current frame"))
        self.connect(self.pastePrevAct, QtCore.SIGNAL("triggered()"), self.pastePrevious)
        
        self.pasteNextAct = QtGui.QAction(self.tr("Paste &Next"), self)
        self.pasteNextAct.setShortcut(self.tr("Ctrl+Shift+V"))
        self.pasteNextAct.setStatusTip(self.tr("Copy the next pose to the "
                                          "current frame"))
        self.connect(self.pasteNextAct, QtCore.SIGNAL("triggered()"), self.pasteNext)
        
        self.extrapolateNextAct = QtGui.QAction(self.tr("&Extrapolate Next"), self)
        #self.extrapolateNextAct.setShortcut(self.tr("Ctrl+E"))
        self.extrapolateNextAct.setStatusTip(self.tr("Extrapolate the next pose "
                                          "based on previous frames"))
        self.connect(self.extrapolateNextAct, QtCore.SIGNAL("triggered()"), self.extrapolateNext)
        
        self.copyROIAct = QtGui.QAction(self.tr("Copy ROI"), self)
        self.copyROIAct.setShortcut(self.tr("Ctrl+R"))
        self.copyROIAct.setStatusTip(self.tr("Copy the current ROI to "
                                          "memory so that it can be copied to other images"))
        self.connect(self.copyROIAct, QtCore.SIGNAL("triggered()"), self.copyROI)
        
        self.pasteROIAct = QtGui.QAction(self.tr("Paste ROI"), self)
        self.pasteROIAct.setShortcut(self.tr("Ctrl+Shift+R"))
        self.pasteROIAct.setStatusTip(self.tr("Paste the ROI from memory  "
                                          "to the current image"))
        self.connect(self.pasteROIAct, QtCore.SIGNAL("triggered()"), self.pasteROI)
        
        self.pasteROIAllAct = QtGui.QAction(self.tr("Paste ROI to all frames"), self)
        self.pasteROIAllAct.setStatusTip(self.tr("Paste the ROI from memory  "
                                          "to the all images"))
        self.connect(self.pasteROIAllAct, QtCore.SIGNAL("triggered()"), self.pasteROIAll)
        
        
        self.plotKinematicsAct = QtGui.QAction(self.tr("&Plot Kinematics"), self)
        self.plotKinematicsAct.setStatusTip(self.tr("Plot the kinematics "
                                                 "for the current models"))
        self.connect(self.plotKinematicsAct, QtCore.SIGNAL("triggered()"), 
                     self.plotKinematics)
        
        self.plotMetricAct = QtGui.QAction(self.tr("&Plot Metric"), self)
        self.plotMetricAct.setStatusTip(self.tr("Plot the metric "
                                                 "parameter space"))
        self.connect(self.plotMetricAct, QtCore.SIGNAL("triggered()"), 
                     self.plotMetric)
        
        self.globalDisplaySettingsAct = QtGui.QAction(self.tr("Global Disp Settings"), self)
        self.globalDisplaySettingsAct.setStatusTip(self.tr("Use the global setting instead "
                                                 "of individual setting for each DRR"))
        self.globalDisplaySettingsAct.setCheckable(True)
        
        self.makePathsRelativeAct = QtGui.QAction(self.tr("Make Paths Relative"), self)
        self.makePathsRelativeAct.setStatusTip(self.tr("When the project is saved "
                                                 "all data paths will be made relative to"
                                                 "a common root directory"))
        self.makePathsRelativeAct.setCheckable(True)
        
        self.registrationSettingsAct = QtGui.QAction(self.tr("&Registration Settings"), self)
        #self.registrationSettingsAct.setShortcut(self.tr("Ctrl+R"))
        self.registrationSettingsAct.setStatusTip(self.tr("Modify the settings "
                                                 "for the registration"))
        self.connect(self.registrationSettingsAct, QtCore.SIGNAL("triggered()"), 
                     self.registrationSettings)
        
        self.metricSettingsAct = QtGui.QAction(self.tr("&Metric Settings"), self)
        self.metricSettingsAct.setShortcut(self.tr("Ctrl+M"))
        self.metricSettingsAct.setStatusTip(self.tr("Modify the settings "
                                                 "for the metric"))
        self.connect(self.metricSettingsAct, QtCore.SIGNAL("triggered()"), 
                     self.metricSettings)
        
        self.optimizerSettingsAct = QtGui.QAction(self.tr("O&ptimizer Settings"), self)
        self.optimizerSettingsAct.setShortcut(self.tr("Ctrl+P"))
        self.optimizerSettingsAct.setStatusTip(self.tr("Modify the settings "
                                                 "for the optimizer"))
        self.connect(self.optimizerSettingsAct, QtCore.SIGNAL("triggered()"), 
                     self.optimizerSettings)
        
                       
        self.aboutAct = QtGui.QAction(self.tr("&About"), self)
        self.aboutAct.setStatusTip(self.tr("Show the application's About box"))
        self.connect(self.aboutAct, QtCore.SIGNAL("triggered()"), self.about)

        self.aboutQtAct = QtGui.QAction(self.tr("About &Qt"), self)
        self.aboutQtAct.setStatusTip(self.tr("Show the Qt library's About box"))
        self.connect(self.aboutQtAct, QtCore.SIGNAL("triggered()"), QtGui.qApp.aboutQt)
        self.connect(self.aboutQtAct, QtCore.SIGNAL("triggered()"), self.aboutQt)


    def createMenus(self):
        self.fileMenu = self.menuBar().addMenu(self.tr("&File"))
        self.fileMenu.addAction(self.newProjectAct)
        self.fileMenu.addAction(self.openProjectAct)
        self.fileMenu.addAction(self.saveProjectAct)
        self.fileMenu.addAction(self.saveAsProjectAct)
        self.fileMenu.addSeparator()
        self.fileMenu.addAction(self.openFixedImageAct)
        self.fileMenu.addAction(self.openCalAct)
        self.fileMenu.addAction(self.openVolumeAct)
        self.fileMenu.addAction(self.openSTLAct)
        self.fileMenu.addSeparator()
        self.fileMenu.addAction(self.exitAct)
        
        self.editMenu = self.menuBar().addMenu(self.tr("&Edit"))
        #self.editMenu.addAction(self.undoAct)
        #self.editMenu.addAction(self.restoreAct)
        #self.editMenu.addSeparator()
        self.editMenu.addAction(self.cutAct)
        self.editMenu.addAction(self.pastePrevAct)
        self.editMenu.addAction(self.pasteNextAct)
        self.editMenu.addSeparator()
        self.editMenu.addAction(self.extrapolateNextAct)
        self.editMenu.addSeparator()
        self.editMenu.addAction(self.copyROIAct)
        self.editMenu.addAction(self.pasteROIAct)
        self.editMenu.addAction(self.pasteROIAllAct)
        
        self.navMenu = self.menuBar().addMenu(self.tr("&Navigation"))
        self.navMenu.addAction(self.navForwardAct)
        self.navMenu.addAction(self.navBackAct)
        self.navMenu.addAction(self.navHomeAct)
        self.navMenu.addAction(self.navEndAct)
        
        self.toolsMenu = self.menuBar().addMenu(self.tr("&Tools"))
        self.toolsMenu.addAction(self.plotKinematicsAct)
        self.toolsMenu.addAction(self.plotMetricAct)
        self.toolsMenu.addSeparator()
        self.toolsMenu.addAction(self.open3DViewerAct)
        self.toolsMenu.addAction(self.convertCalAct)
        
        self.settingsMenu = self.menuBar().addMenu(self.tr("&Settings"))
        self.settingsMenu.addAction(self.registrationSettingsAct)
        self.settingsMenu.addAction(self.metricSettingsAct)
        self.settingsMenu.addAction(self.optimizerSettingsAct)
        self.settingsMenu.addSeparator()
        self.settingsMenu.addAction(self.globalDisplaySettingsAct)
        self.settingsMenu.addAction(self.makePathsRelativeAct)
        
        self.helpMenu = self.menuBar().addMenu(self.tr("&Help"))
        self.helpMenu.addAction(self.aboutAct)
        self.helpMenu.addAction(self.aboutQtAct)
    
    def updatePoseLabel(self,pose,flag='current'):
        # allow the pose to be displayed differently if it is optimized
        if flag == 'current':
            self.poseLabel.setText("<font color=black><b>X:</b>  %4.1f   <b>Y:</b>  %4.1f   <b>Z:</b>  "\
                                   "%4.1f   <b>RotX:</b>  %4.1f   <b>RotY</b>:  %4.1f   <b>RotZ:</b>  %4.1f   " % 
                                   (pose[0],pose[1],pose[2],
                                    pose[3]*180/np.pi,pose[4]*180/np.pi,pose[5]*180/np.pi))

        elif flag == 'optimized':
            self.poseLabel.setText("<font color=red><b>X:</b>  %4.1f   <b>Y:</b>  %4.1f   <b>Z:</b>  "\
                                   "%4.1f   <b>RotX:</b>  %4.1f   <b>RotY</b>:  %4.1f   <b>RotZ:</b>  %4.1f  " % 
                                   (pose[0],pose[1],pose[2],
                                    pose[3]*180/np.pi,pose[4]*180/np.pi,pose[5]*180/np.pi))
    def setupDecorations(self):
        self.statusBar().showMessage(self.tr("Image Registration in python"))
        self.statusBar().setSizeGripEnabled(0)
        self.poseLabel = QtGui.QLabel('pose')
        self.poseLabel.setTextFormat(QtCore.Qt.RichText)
        self.statusBar().addPermanentWidget(self.poseLabel)
        self.setWindowTitle(self.tr("pyTrack"))
        self.setWindowIcon(QtGui.QIcon('icon.png'))
        self.updatePoseLabel([0,0,0,0,0,0])
        
class RedirectText(object):
    def __init__(self, aQTextEdit):
        self.out = aQTextEdit
        self.i = 0
 
    def write(self, string):
        if string == '\n':
            return
        time_stamp = time.strftime('%H:%M::%S')
        #string = QtCore.QString(string).simplified()
        formatted_string = "<font color=green>[%i] %s pyJT>>> </font> %s" % \
                            (self.i,time_stamp,string)
        self.out.append(formatted_string)
        self.out.ensureCursorVisible()
        self.i += 1

class RedirectErr(object):
    def __init__(self, aQTextEdit):
        self.out = aQTextEdit
        #self.i = 0
 
    def write(self, string):
        if string == '\n':
            return
        time_stamp = time.strftime('%H:%M::%S')
        #string = QtCore.QString(string).simplified()
        formatted_string = "<font color=red>[%i] %s pyJT>>> %s</font>" % \
                            (self.i,time_stamp,string)
        self.out.append(formatted_string)
        self.i += 1

class MyFixedImageListModel(QtCore.QAbstractListModel): 
    def __init__(self, DataStructure, parent=None, *args): 
        """ 
        Give a data structure (Data2.DataStructure, from xml) and a key that 
        will be used to search the structure and populate the list.
            For example, tags could be: (Volume,FixedImage,Cal,etc.)
        A stripped path (just the base name) will be returned
        """
        QtCore.QAbstractListModel.__init__(self, parent, *args) 
        #self.listdata = datain
        self.DataStructure = DataStructure
        self.tag = 'FixedImage' # this class could be extended to used for other data types
 
    def rowCount(self, parent=QtCore.QModelIndex()): 
        count = len(self.DataStructure.get_path_list(self.tag))
        return  count
 
    def data(self, index, role): 
        if index.isValid() and (role == QtCore.Qt.DisplayRole): 
            path_list = self.DataStructure.get_path_list(self.tag) 
            name = QtCore.QFileInfo(str(path_list[index.row()])).fileName()              
            return QtCore.QVariant(name)
        elif index.isValid() and (role == QtCore.Qt.CheckStateRole):
            path_list = self.DataStructure.get_path_list(self.tag)
            vol_path = self.DataStructure.get_current_volume_path()
            isoptimized = self.DataStructure.isoptimized(path_list[index.row()],
                                                         vol_path)
            if isoptimized:
                return QtCore.QVariant(QtCore.Qt.Checked)
            else:
                return QtCore.QVariant(QtCore.Qt.Unchecked)
        else: 
            return QtCore.QVariant()
    def flags(self,flag):
        return (QtCore.Qt.ItemIsEnabled|QtCore.Qt.ItemIsSelectable)
            
    def setDataStructure(self,DataStructure):
        self.reset()
        self.DataStructure = DataStructure

class MyListModel(QtCore.QAbstractListModel): 
    def __init__(self, datain, parent=None, *args): 
        """ 
        datain is a list of data to be displayed in the view.
        """
        QtCore.QAbstractListModel.__init__(self, parent, *args) 
        self.listdata = datain
        self.checked = []
 
    def rowCount(self, parent=QtCore.QModelIndex()): 
        return len(self.listdata) 
 
    def data(self, index, role): 
        if index.isValid() and role == QtCore.Qt.DisplayRole:
            return QtCore.QVariant(self.listdata[index.row()])
        elif index.isValid() and role == QtCore.Qt.CheckStateRole:
            # TODO: need to store which volumes are checked by the user 
            # the ones that are checked should be visible and the others 
            # should be hidden.  Right now they will always be checked
            #self.checked[index.row] = index.checkState()
            #if index.checkState
            #print index
            return QtCore.QVariant(self.checked[index.row()])
        else: 
            return QtCore.QVariant()
    
    def flags(self,flag):
        return (QtCore.Qt.ItemIsEnabled|
                QtCore.Qt.ItemIsUserCheckable|
                QtCore.Qt.ItemIsSelectable)
            
    def setList(self,datain):
        self.reset()
        self.listdata = datain
        # Start off with all but the first item unchecked
        for i in datain:
            self.checked.append(0)
        #self.checked[0] = 2

class MyTreeModel(QtGui.QTreeWidgetItem):
    pass

class MyMplCanvas(FigureCanvas):
    """Ultimately, this is a QWidget (as well as a FigureCanvasAgg, etc.)."""
    def __init__(self, parent=None):
        width=5
        height=4
        dpi=100
        self.fig = Figure(facecolor='black', edgecolor='black')
        #self.axes = self.fig.add_subplot(111)
        # We want the axes cleared every time plot() is called
        #self.axes.hold(False)

        #self.compute_initial_figure()
        
        FigureCanvas.__init__(self, self.fig)
        #self.reparent(parent, QPoint(0, 0))

        FigureCanvas.setSizePolicy(self,
                                   QtGui.QSizePolicy.Expanding,
                                   QtGui.QSizePolicy.Expanding)
        FigureCanvas.updateGeometry(self)

    def sizeHint(self):
        w, h = self.get_width_height()
        return QtCore.QSize(w, h)

    def minimumSizeHint(self):
        return QtCore.QSize(100, 50)   
    
    def compute_initial_figure(self):
        pass
        t = arange(0.0, 3.0, 0.01)
        s = sin(2*pi*t)
        self.axes.plot(t, s)     
        

class OrthoViewer(QtGui.QFrame):
    """
    """
    def __init__(self,parent=None,*args):
        """Constructor"""
        QtGui.QFrame.__init__(self, parent, *args) 
        orthoViewLayout = QtGui.QVBoxLayout()
        self.setLayout(orthoViewLayout)
        apViewFrame = QtGui.QFrame()
        mlViewFrame = QtGui.QFrame()
        siViewFrame = QtGui.QFrame()
        
        orthoViewLayout.addWidget(apViewFrame)
        orthoViewLayout.addWidget(mlViewFrame)
        orthoViewLayout.addWidget(siViewFrame)
        
        #self.apViewIren = QVTKRenderWindowInteractor(apViewFrame)
        self.apView = JT.QTDRR(apViewFrame)
        self.apViewIren = self.apView.iren
        #self.apViewIren.Initialize()
        style = vtk.vtkInteractorStyleTrackballActor()
        #self.apViewIren.SetInteractorStyle(style)
#        self.apViewRen = JT.vtk.vtkRenderer()
#        self.apViewRenWin = self.apViewIren.GetRenderWindow()
#        self.apViewRenWin.AddRenderer(self.apViewRen)
#        self.apCam = self.apViewRen.GetActiveCamera()
#        self.apCam.SetPosition(1,0,0)
#        self.apCam.SetFocalPoint(0,0,0)
#        self.apCam.SetViewUp(0,0,1)
#        self.apViewRen.ResetCamera()
        #viewUp = apCam.GetViewUp()
        #projDir = apCam.GetDirectionOfProjection()
        #viewRight = np.cross(projDir,viewUp)
        
        def syncViews(object,event):
            self.apViewRen.ResetCamera()
            self.mlViewRenWin.Render()
            self.mlViewRen.ResetCamera()
            self.siViewRenWin.Render()
            self.siViewRen.ResetCamera()
            
        #self.mlViewIren = QVTKRenderWindowInteractor(mlViewFrame)
        self.mlView = JT.QTDRR(mlViewFrame)
        self.mlViewIren = self.mlView.iren
        #self.apViewIren.Initialize()
        #self.mlViewIren.SetInteractorStyle(style)
#        self.mlViewRen = JT.vtk.vtkRenderer()
#        self.mlViewRenWin = self.mlViewIren.GetRenderWindow()
#        self.mlViewRenWin.AddRenderer(self.mlViewRen)
#        self.mlCam = self.mlViewRen.GetActiveCamera()
#        self.mlCam.SetPosition(0,1,0)
#        self.mlCam.SetFocalPoint(0,0,0)
#        self.mlCam.SetViewUp(0,0,1)
#        self.mlViewRen.ResetCamera()
        
        #self.siViewIren = QVTKRenderWindowInteractor(siViewFrame)
        self.siView = JT.QTDRR(siViewFrame)
        self.siViewIren = self.siView.iren
        #self.siViewIren.Initialize()
        #self.siViewIren.SetInteractorStyle(style)
#        self.siViewRen = JT.vtk.vtkRenderer()
#        self.siViewRenWin = self.siViewIren.GetRenderWindow()
#        self.siViewRenWin.AddRenderer(self.siViewRen)
#        self.siCam = self.siViewRen.GetActiveCamera()
#        self.siCam.SetPosition(0,0,1)
#        self.siCam.SetFocalPoint(0,0,0)
#        self.siCam.SetViewUp(1,0,0)
#        self.siViewRen.ResetCamera()
        
        # Transfer interactions from ap view to other views (synchoronize)
        #style.AddObserver("MouseMoveEvent",syncViews)
        #style.AddObserver("RightButtonPressEvent",syncViews)
        #style.AddObserver("RightButtonReleaseEvent",syncViews)

#        apViewFrame.setFixedSize(200,200)
#        self.apViewIren.setFixedSize(200,200)
#        
#        mlViewFrame.setFixedSize(200,200)
#        self.mlViewIren.setFixedSize(200,200)
#        
#        siViewFrame.setFixedSize(200,200)
#        self.siViewIren.setFixedSize(200,200)
    
    def SetModel(self,model):
        #model.SetPosition(0,0,0)
        if isinstance(model,vtk.vtkVolume):
            self.apView.AddVolume(model)
            self.apView.Render()
            self.mlView.AddVolume(model)
            self.mlView.Render()
            self.siView.AddVolume(model)
            self.siView.Render()
        else:
            self.apViewRen.AddActor(model)
            self.apViewRen.Render()
            self.mlViewRen.AddActor(model)
            self.mlViewRen.Render()
            self.siViewRen.AddActor(model)
            self.siViewRen.Render()
        
#    def SetFocalLength(self,focal_length):
#        """
#        postion the cameras so that they provide the 
#        """
#        self.apCam.SetPosition(focal_length,0,0)
#        self.mlCam.SetPosition(0,focal_length,0)
#        self.siCam.SetPosition(0,0,focal_length)
    
    def SetModelPosition(self,position):
        """
        tell the cameras where to point
        """
        dist = JT.utils.mag(position)
        self.apCam.SetFocalPoint(position)
        self.apCam.SetPosition(position[0]+dist,position[1],position[2])
        self.mlCam.SetFocalPoint(position)
        self.mlCam.SetPosition(position[0],position[1],position[2]+dist)
        self.siCam.SetFocalPoint(position)
        self.siCam.SetPosition(position[0],position[1]+dist,position[2])
    
    def SetVolumeFileName(self,volume_path):
        self.apView.SetVolumeFileName(volume_path)
        self.mlView.SetVolumeFileName(volume_path)
        self.siView.SetVolumeFileName(volume_path)
    
    def LoadStaFile(self,sta_path):
        self.apView.LoadStaFile(sta_path)
        self.mlView.LoadStaFile(sta_path)
        self.siView.LoadStaFile(sta_path)
        
    def Render(self):
        self.apView.renWin.Render()
        self.mlView.renWin.Render()
        self.siView.renWin.Render()
    
    def Start(self):
        self.apView.iren.Start()
        self.mlView.iren.Start()
        self.siView.iren.Start()
        
    def LoadCalFile(self,cal_path):
        """
        To orient the three views, the cal must be set for each
        """
        self.apView.LoadCalFile(cal_path)
        self.mlView.LoadCalFile(cal_path)
        self.siView.LoadCalFile(cal_path)
        
        
    def test(self):
        QtGui.QApplication.setStyle('cleanlooks')
        app = QtGui.QApplication(sys.argv) 
        mainWindow = QtGui.QMainWindow()
        #mainWindow.setFixedSize(512,512)
        orthoViewer = OrthoViewer()
    
        mainWindow.setCentralWidget(orthoViewer)
        cone = get_cone()
        orthoViewer.SetModel(cone)
        
        mainWindow.show() 
            
        app.exec_()
    
    def volume_test(self):
        QtGui.QApplication.setStyle('cleanlooks')
        app = QtGui.QApplication(sys.argv) 
        mainWindow = QtGui.QMainWindow()

        orthoViewer = OrthoViewer()
        mainWindow.setCentralWidget(orthoViewer)
        vol_path = r'C:/Users/bryan/phd/data/CT/9491/9492/Spine1_C4_uchar.mhd'
        cal_path = r'C:\Users\bryan\phd\data\Spine1_8\Movies\crop_512\Calibration_cal_m.cal'
        position = [-36.5386, 0.90431, -399.1787]
        pose = [-36.538665300927136, 0.90431097918240777, -399.17877670961417, 
                0.079916111893689218, -1.5370885329320032, 1.4290161358684492]
        
        
#        vol_path = r'C:\Users\bryan\phd\data\CT\Spine3.mhd'
#        sta_path = 'C:/Users/bryan/bryan-code/2D3D/vert1/CT/ucharCTv1r1.sta'
#        cal_path = 'C:/Users/bryan/bryan-code/2D3D/vert2/fluoro/ushortim040-LAT.cal'
        
        orthoViewer.SetVolumeFileName(vol_path)
        orthoViewer.LoadCalFile(cal_path)
        orthoViewer.LoadStaFile(sta_path)
    
#        volume = JT.Volume2()
#        volume.SetPose(pose)
#        volume.SetVolumeFileName(vol_path)
#        orthoViewer.SetCalibration(cal_path)
#        orthoViewer.SetModel(volume)
#        orthoViewer.SetModelPosition(position)
        orthoViewer.Render()
        orthoViewer.Start()
        
        mainWindow.show() 
        app.exec_()
        
       
class DialogBuilder(QtGui.QDialog):
    def __init__(self, settings, windowTitle,parent=None):
        QtGui.QDialog.__init__(self, parent)
        
        #self.setGeometry(300, 300, 350, 80)
        self.setWindowTitle(windowTitle)
        self.gridLayout = QtGui.QGridLayout()
        self.original_settings = settings
        self.setLayout(self.gridLayout)
        self.labelList = {}
        self.inputList = {}
        
        for i,field in enumerate(settings):
            self.labelList[field] = QtGui.QLabel(str(field))
            self.inputList[field] = QtGui.QLineEdit()
            self.inputList[field].setText(str(settings[field]))
            self.gridLayout.addWidget(self.labelList[field],i,0)
            self.gridLayout.addWidget(self.inputList[field],i,1)
            self.connect(self.inputList[field],
                         QtCore.SIGNAL("textEdited (const QString&)"),
                         self.updateText)
            
        self.addButton = QtGui.QPushButton('Add Field')
        self.connect(self.addButton,
                         QtCore.SIGNAL("clicked()"),
                         self.addField)
        self.button = QtGui.QPushButton('OK')
        self.connect(self.button,
                         QtCore.SIGNAL("clicked()"),
                         self.closeDialog)
        
        self.gridLayout.addWidget(self.addButton,self.gridLayout.count()+1,0)
        self.gridLayout.addWidget(self.button,self.gridLayout.count()+1,1)
        
        
        self.updateText()
        
    def updateText(self):
        self.data = {}
        for field in self.inputList:
            self.data[str(field)] = str(self.inputList[field].text())
            
    def closeDialog(self):
        self.done(1)
    
    def addField(self):
        #ok = QtCore.Qt.WindowFlags()
        field,ok = QtGui.QInputDialog.getText(None, "Enter new Field Name",
                                          "Field name:", 
                                          QtGui.QLineEdit.Normal)
        if not ok: return
        
        i = self.gridLayout.count()
        self.labelList[field] = QtGui.QLabel(str(field))
        self.inputList[field] = QtGui.QLineEdit()
        self.gridLayout.removeWidget(self.addButton)
        self.gridLayout.removeWidget(self.button)
        self.gridLayout.addWidget(self.labelList[field],i,0)
        self.gridLayout.addWidget(self.inputList[field],i,1)
        
        self.connect(self.inputList[field],
                     QtCore.SIGNAL("textEdited (const QString&)"),
                     self.updateText)
        self.gridLayout.addWidget(self.addButton,i+1,0)
        self.gridLayout.addWidget(self.button,i+1,1)
        

def get_cone(resolution=10):
    cone = vtk.vtkConeSource()
    cone.SetResolution(resolution)
    
    coneMapper = vtk.vtkPolyDataMapper() 
    coneMapper.SetInput(cone.GetOutput()) 
    coneMapper.SetScalarRange(0,1)
    
    coneActor = vtk.vtkActor() 
    coneActor.SetMapper(coneMapper)
    return coneActor

def show_image():
    """
    Return a vtk cone actor with resolution
    """
    QtGui.QApplication.setStyle('cleanlooks')
    app = QtGui.QApplication(sys.argv) 
    mainWindow = QtGui.QMainWindow()
    mainWindow.setFixedSize(512,512)
    frameWidget = QtGui.QFrame(mainWindow)

    mainWindow.setCentralWidget(frameWidget)
    iren = JT.qtImageCanvas.ImageCanvas(frameWidget) 

    iren.setFixedSize(512,512)
    filename = 'C:/Users/bryan/bryan-code/2D3D/vert1/fluoro/ushortim080-LAT.mhd'
    iren.SetFileName(filename)
    
    #iren.Initialize()
    
    mainWindow.show() 
    app.exec_()

if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    mainwindow = MainWindow()
    mainwindow.show()
    sys.exit(app.exec_())
    